Skip to content

Commit 9e3ca45

Browse files
rdeltourWilcoFiers
authored andcommitted
feat: Improve generated selectors for namespaced elements in XHTML (#582)
* 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
1 parent 48d8703 commit 9e3ca45

File tree

8 files changed

+130
-4
lines changed

8 files changed

+130
-4
lines changed

lib/core/utils/get-selector.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const escapeSelector = axe.utils.escapeSelector;
2+
let isXHTML;
23

34
function isUncommonClassName (className) {
45
return ![
@@ -27,7 +28,7 @@ function getDistinctClassList (elm) {
2728
}
2829

2930
const commonNodes = [
30-
'div', 'span', 'p',
31+
'div', 'span', 'p',
3132
'b', 'i', 'u', 'strong', 'em',
3233
'h2', 'h3'
3334
];
@@ -79,7 +80,6 @@ const createSelector = {
7980
// Get uncommon node names
8081
getUncommonElm (elm, { isCommonElm, isCustomElm, nodeName }) {
8182
if (!isCommonElm && !isCustomElm) {
82-
nodeName = escapeSelector(nodeName);
8383
// Add [type] if nodeName is an input element
8484
if (nodeName === 'input' && elm.hasAttribute('type')) {
8585
nodeName += '[type="' + elm.type + '"]';
@@ -130,7 +130,12 @@ const createSelector = {
130130
* recognize the element by (IDs, aria roles, custom element names, etc.)
131131
*/
132132
function getElmFeatures (elm, featureCount) {
133-
const nodeName = elm.nodeName.toLowerCase();
133+
if (typeof isXHTML === 'undefined') {
134+
isXHTML = axe.utils.isXHTML(document);
135+
}
136+
const nodeName = escapeSelector(isXHTML?
137+
elm.localName
138+
:elm.nodeName.toLowerCase());
134139
const classList = Array.from(elm.classList) || [];
135140
// Collect some props we need to build the selector
136141
const props = {
@@ -174,7 +179,7 @@ function generateSelector (elm, options, doc) {
174179
let { isUnique = false } = options;
175180
const idSelector = createSelector.getElmId(elm);
176181
const {
177-
featureCount = 2,
182+
featureCount = 2,
178183
minDepth = 1,
179184
toRoot = false,
180185
childSelectors = []

lib/core/utils/is-xhtml.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
2+
/**
3+
* Determines if a document node is XHTML
4+
* @method isXHTML
5+
* @memberof axe.utils
6+
* @instance
7+
* @param {Node} doc a document node
8+
* @return {Boolean}
9+
*/
10+
axe.utils.isXHTML = function (doc) {
11+
'use strict';
12+
if (!doc.createElement) {
13+
return false;
14+
}
15+
return doc.createElement('A').localName === 'A';
16+
};

test/core/utils/get-selector.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,20 @@ describe('axe.utils.getSelector', function () {
194194
assert.equal(result[0], node);
195195
});
196196

197+
it('should work on complex namespaced elements', function () {
198+
fixture.innerHTML = '<m:math xmlns:m="http://www.w3.org/1998/Math/MathML">' +
199+
'<m:mi>x</m:mi>' +
200+
'<m:annotation-xml encoding="MathML-Content">' +
201+
'<m:ci>x</m:ci>' +
202+
'</m:annotation-xml>' +
203+
'</m:math>';
204+
var node = fixture.querySelector('m\\:ci');
205+
var sel = axe.utils.getSelector(node);
206+
var result = document.querySelectorAll(sel);
207+
assert.lengthOf(result, 1);
208+
assert.equal(result[0], node);
209+
});
210+
197211
it('shouldn\'t fail if the node\'s parentNode doesnt have children, somehow (Firefox bug)', function () {
198212
var sel = axe.utils.getSelector({
199213
nodeName: 'a',

test/core/utils/is-xhtml.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
describe('axe.utils.isXHTML', function () {
2+
'use strict';
3+
4+
it('should be a function', function () {
5+
assert.isFunction(axe.utils.isXHTML);
6+
});
7+
8+
it('should return true on any document that is XHTML', function () {
9+
var doc = document.implementation.createDocument('http://www.w3.org/1999/xhtml', 'html', null);
10+
assert.isTrue(axe.utils.isXHTML(doc));
11+
});
12+
13+
it('should return false on any document that is HTML', function () {
14+
var doc = document.implementation.createHTMLDocument('Monkeys');
15+
assert.isFalse(axe.utils.isXHTML(doc));
16+
});
17+
18+
it('should return false on any document that is HTML - fixture', function () {
19+
assert.isFalse(axe.utils.isXHTML(document));
20+
});
21+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
describe('axe.utils.getSelector', function () {
3+
'use strict';
4+
it('should work on namespaced elements', function () {
5+
var fixture = document.querySelector('#fixture');
6+
var node = fixture.firstElementChild;
7+
var sel = axe.utils.getSelector(node);
8+
var result = document.querySelectorAll(sel);
9+
assert.lengthOf(result, 1);
10+
assert.equal(result[0], node);
11+
});
12+
});
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
2+
<head>
3+
<title>axe.utils.getSelector test</title>
4+
<meta charset="utf-8"/>
5+
<link rel="stylesheet" type="text/css" href="/node_modules/mocha/mocha.css" />
6+
<script src="/node_modules/mocha/mocha.js"></script>
7+
<script src="/node_modules/chai/chai.js"></script>
8+
<script src="/axe.js"></script>
9+
<script>
10+
mocha.setup({
11+
timeout: 10000,
12+
ui: 'bdd'
13+
});
14+
var assert = chai.assert;
15+
</script>
16+
</head>
17+
<body>
18+
<div id="fixture">
19+
<m:math xmlns:m="http://www.w3.org/1998/Math/MathML"></m:math>
20+
</div>
21+
<div id="mocha"></div>
22+
<script src="get-selector.js"></script>
23+
<script src="/test/integration/adapter.js"></script>
24+
</body>
25+
</html>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2+
describe('axe.utils.isXHTML', function () {
3+
'use strict';
4+
5+
it('should return true on any document that is XHTML', function () {
6+
assert.isTrue(axe.utils.isXHTML(document));
7+
});
8+
9+
});
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
2+
<head>
3+
<title>axe.utils.isXHTML test</title>
4+
<meta charset="utf-8"/>
5+
<link rel="stylesheet" type="text/css" href="/node_modules/mocha/mocha.css" />
6+
<script src="/node_modules/mocha/mocha.js"></script>
7+
<script src="/node_modules/chai/chai.js"></script>
8+
<script src="/axe.js"></script>
9+
<script>
10+
mocha.setup({
11+
timeout: 10000,
12+
ui: 'bdd'
13+
});
14+
var assert = chai.assert;
15+
</script>
16+
</head>
17+
<body>
18+
<div id="fixture">
19+
</div>
20+
<div id="mocha"></div>
21+
<script src="is-xhtml.js"></script>
22+
<script src="/test/integration/adapter.js"></script>
23+
</body>
24+
</html>

0 commit comments

Comments
 (0)