Skip to content

Commit

Permalink
feat: Improve generated selectors for namespaced elements in XHTML (#582
Browse files Browse the repository at this point in the history
)

* feat(utils): add function `isXHTML` to test if a document node is XHTML

* test(utils): add a test for `axe.utils.isXHTML` on an XHTML document

* fix(getSelector): improve selectors for namespaced elements

- by default, ensure the nodename is escaped
- for XHTML documents, only use the local name

Replaces #566
Closes #563

* test(getSelector): add a test for `axe.utils.getSelector` on a namespaced XHTML element
  • Loading branch information
rdeltour authored and WilcoFiers committed Dec 15, 2017
1 parent 48d8703 commit 9e3ca45
Show file tree
Hide file tree
Showing 8 changed files with 130 additions and 4 deletions.
13 changes: 9 additions & 4 deletions lib/core/utils/get-selector.js
@@ -1,4 +1,5 @@
const escapeSelector = axe.utils.escapeSelector;
let isXHTML;

function isUncommonClassName (className) {
return ![
Expand Down Expand Up @@ -27,7 +28,7 @@ function getDistinctClassList (elm) {
}

const commonNodes = [
'div', 'span', 'p',
'div', 'span', 'p',
'b', 'i', 'u', 'strong', 'em',
'h2', 'h3'
];
Expand Down Expand Up @@ -79,7 +80,6 @@ const createSelector = {
// Get uncommon node names
getUncommonElm (elm, { isCommonElm, isCustomElm, nodeName }) {
if (!isCommonElm && !isCustomElm) {
nodeName = escapeSelector(nodeName);
// Add [type] if nodeName is an input element
if (nodeName === 'input' && elm.hasAttribute('type')) {
nodeName += '[type="' + elm.type + '"]';
Expand Down Expand Up @@ -130,7 +130,12 @@ const createSelector = {
* recognize the element by (IDs, aria roles, custom element names, etc.)
*/
function getElmFeatures (elm, featureCount) {
const nodeName = elm.nodeName.toLowerCase();
if (typeof isXHTML === 'undefined') {
isXHTML = axe.utils.isXHTML(document);
}
const nodeName = escapeSelector(isXHTML?
elm.localName
:elm.nodeName.toLowerCase());
const classList = Array.from(elm.classList) || [];
// Collect some props we need to build the selector
const props = {
Expand Down Expand Up @@ -174,7 +179,7 @@ function generateSelector (elm, options, doc) {
let { isUnique = false } = options;
const idSelector = createSelector.getElmId(elm);
const {
featureCount = 2,
featureCount = 2,
minDepth = 1,
toRoot = false,
childSelectors = []
Expand Down
16 changes: 16 additions & 0 deletions lib/core/utils/is-xhtml.js
@@ -0,0 +1,16 @@

/**
* Determines if a document node is XHTML
* @method isXHTML
* @memberof axe.utils
* @instance
* @param {Node} doc a document node
* @return {Boolean}
*/
axe.utils.isXHTML = function (doc) {
'use strict';
if (!doc.createElement) {
return false;
}
return doc.createElement('A').localName === 'A';
};
14 changes: 14 additions & 0 deletions test/core/utils/get-selector.js
Expand Up @@ -194,6 +194,20 @@ describe('axe.utils.getSelector', function () {
assert.equal(result[0], node);
});

it('should work on complex namespaced elements', function () {
fixture.innerHTML = '<m:math xmlns:m="http://www.w3.org/1998/Math/MathML">' +
'<m:mi>x</m:mi>' +
'<m:annotation-xml encoding="MathML-Content">' +
'<m:ci>x</m:ci>' +
'</m:annotation-xml>' +
'</m:math>';
var node = fixture.querySelector('m\\:ci');
var sel = axe.utils.getSelector(node);
var result = document.querySelectorAll(sel);
assert.lengthOf(result, 1);
assert.equal(result[0], node);
});

it('shouldn\'t fail if the node\'s parentNode doesnt have children, somehow (Firefox bug)', function () {
var sel = axe.utils.getSelector({
nodeName: 'a',
Expand Down
21 changes: 21 additions & 0 deletions test/core/utils/is-xhtml.js
@@ -0,0 +1,21 @@
describe('axe.utils.isXHTML', function () {
'use strict';

it('should be a function', function () {
assert.isFunction(axe.utils.isXHTML);
});

it('should return true on any document that is XHTML', function () {
var doc = document.implementation.createDocument('http://www.w3.org/1999/xhtml', 'html', null);
assert.isTrue(axe.utils.isXHTML(doc));
});

it('should return false on any document that is HTML', function () {
var doc = document.implementation.createHTMLDocument('Monkeys');
assert.isFalse(axe.utils.isXHTML(doc));
});

it('should return false on any document that is HTML - fixture', function () {
assert.isFalse(axe.utils.isXHTML(document));
});
});
12 changes: 12 additions & 0 deletions test/integration/full/get-selector/get-selector.js
@@ -0,0 +1,12 @@

describe('axe.utils.getSelector', function () {
'use strict';
it('should work on namespaced elements', function () {
var fixture = document.querySelector('#fixture');
var node = fixture.firstElementChild;
var sel = axe.utils.getSelector(node);
var result = document.querySelectorAll(sel);
assert.lengthOf(result, 1);
assert.equal(result[0], node);
});
});
25 changes: 25 additions & 0 deletions test/integration/full/get-selector/get-selector.xhtml
@@ -0,0 +1,25 @@
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>axe.utils.getSelector test</title>
<meta charset="utf-8"/>
<link rel="stylesheet" type="text/css" href="/node_modules/mocha/mocha.css" />
<script src="/node_modules/mocha/mocha.js"></script>
<script src="/node_modules/chai/chai.js"></script>
<script src="/axe.js"></script>
<script>
mocha.setup({
timeout: 10000,
ui: 'bdd'
});
var assert = chai.assert;
</script>
</head>
<body>
<div id="fixture">
<m:math xmlns:m="http://www.w3.org/1998/Math/MathML"></m:math>
</div>
<div id="mocha"></div>
<script src="get-selector.js"></script>
<script src="/test/integration/adapter.js"></script>
</body>
</html>
9 changes: 9 additions & 0 deletions test/integration/full/is-xhtml/is-xhtml.js
@@ -0,0 +1,9 @@

describe('axe.utils.isXHTML', function () {
'use strict';

it('should return true on any document that is XHTML', function () {
assert.isTrue(axe.utils.isXHTML(document));
});

});
24 changes: 24 additions & 0 deletions test/integration/full/is-xhtml/is-xhtml.xhtml
@@ -0,0 +1,24 @@
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>axe.utils.isXHTML test</title>
<meta charset="utf-8"/>
<link rel="stylesheet" type="text/css" href="/node_modules/mocha/mocha.css" />
<script src="/node_modules/mocha/mocha.js"></script>
<script src="/node_modules/chai/chai.js"></script>
<script src="/axe.js"></script>
<script>
mocha.setup({
timeout: 10000,
ui: 'bdd'
});
var assert = chai.assert;
</script>
</head>
<body>
<div id="fixture">
</div>
<div id="mocha"></div>
<script src="is-xhtml.js"></script>
<script src="/test/integration/adapter.js"></script>
</body>
</html>

0 comments on commit 9e3ca45

Please sign in to comment.