Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 24 additions & 14 deletions lib/commons/dom/idrefs.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import getRootNode from './get-root-node';
import { tokenList } from '../../core/utils';
import {
tokenList,
nodeLookup,
closest,
querySelectorAll
} from '../../core/utils';

/**
* Get elements referenced via a space-separated token attribute;
Expand All @@ -18,24 +23,29 @@ import { tokenList } from '../../core/utils';
*
*/
function idrefs(node, attr) {
node = node.actualNode || node;
const { vNode, domNode } = nodeLookup(node);
const result = [];
let doc;
const attrValue = vNode?.attr(attr) ?? node.getAttribute(attr);

try {
const doc = getRootNode(node);
const result = [];
let attrValue = node.getAttribute(attr);
if (attrValue) {
for (const token of tokenList(attrValue)) {
try {
doc ??= getRootNode(domNode);
result.push(doc.getElementById(token));
} catch {
// don't run QSA on detached nodes or partial trees
const root = closest(vNode, 'html');
if (!root) {
throw new TypeError('Cannot resolve id references for non-DOM nodes');
}

if (attrValue) {
attrValue = tokenList(attrValue);
for (let index = 0; index < attrValue.length; index++) {
result.push(doc.getElementById(attrValue[index]));
result.push(querySelectorAll(root, `#${token}`, vNode.shadowId)?.[0]);
}
}

return result;
} catch {
throw new TypeError('Cannot resolve id references for non-DOM nodes');
}

return result;
}

export default idrefs;
6 changes: 3 additions & 3 deletions lib/commons/text/accessible-text.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import accessibleTextVirtual from './accessible-text-virtual';
import { getNodeFromTree } from '../../core/utils';
import { nodeLookup } from '../../core/utils';

/**
* Finds virtual node and calls accessibleTextVirtual()
Expand All @@ -12,8 +12,8 @@ import { getNodeFromTree } from '../../core/utils';
* @return {string}
*/
function accessibleText(element, context) {
const virtualNode = getNodeFromTree(element); // throws an exception on purpose if axe._tree not correct
Copy link
Contributor Author

@straker straker Aug 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is not true as getNodeFromTree does not throw, just returns null if the node isn't found.

return accessibleTextVirtual(virtualNode, context);
const { vNode } = nodeLookup(element); // throws an exception on purpose if axe._tree not correct
return accessibleTextVirtual(vNode, context);
}

export default accessibleText;
70 changes: 34 additions & 36 deletions test/commons/dom/idrefs.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,36 @@
function createContentIDR() {
'use strict';
var group = document.createElement('div');
const group = document.createElement('div');
group.id = 'target';
return group;
}

function makeShadowTreeIDR(node) {
'use strict';
var root = node.attachShadow({ mode: 'open' });
var div = document.createElement('div');
const root = node.attachShadow({ mode: 'open' });
const div = document.createElement('div');
div.className = 'parent';
div.setAttribute('target', 'target');
root.appendChild(div);
div.appendChild(createContentIDR());
}

describe('dom.idrefs', function () {
describe('dom.idrefs', () => {
'use strict';

var fixture = document.getElementById('fixture');
var shadowSupported = axe.testUtils.shadowSupport.v1;
const fixture = document.getElementById('fixture');
const shadowSupported = axe.testUtils.shadowSupport.v1;

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

it('should find referenced nodes by ID', function () {
it('should find referenced nodes by ID', () => {
fixture.innerHTML =
'<div aria-cats="target1 target2" id="start"></div>' +
'<div id="target1"></div><div id="target2"></div>';

var start = document.getElementById('start'),
expected = [
document.getElementById('target1'),
document.getElementById('target2')
];
const start = document.getElementById('start');
const expected = [
document.getElementById('target1'),
document.getElementById('target2')
];

assert.deepEqual(
axe.commons.dom.idrefs(start, 'aria-cats'),
Expand All @@ -45,13 +41,13 @@ describe('dom.idrefs', function () {

(shadowSupported ? it : xit)(
'should find only referenced nodes within the current root: shadow DOM',
function () {
() => {
// shadow DOM v1 - note: v0 is compatible with this code, so no need
// to specifically test this
fixture.innerHTML = '<div target="target"><div id="target"></div></div>';
makeShadowTreeIDR(fixture.firstChild);
var start = fixture.firstChild.shadowRoot.querySelector('.parent');
var expected = [fixture.firstChild.shadowRoot.getElementById('target')];
const start = fixture.firstChild.shadowRoot.querySelector('.parent');
const expected = [fixture.firstChild.shadowRoot.getElementById('target')];

assert.deepEqual(
axe.commons.dom.idrefs(start, 'target'),
Expand All @@ -63,14 +59,14 @@ describe('dom.idrefs', function () {

(shadowSupported ? it : xit)(
'should find only referenced nodes within the current root: document',
function () {
() => {
// shadow DOM v1 - note: v0 is compatible with this code, so no need
// to specifically test this
fixture.innerHTML =
'<div target="target" class="parent"><div id="target"></div></div>';
makeShadowTreeIDR(fixture.firstChild);
var start = fixture.querySelector('.parent');
var expected = [document.getElementById('target')];
const start = fixture.querySelector('.parent');
const expected = [document.getElementById('target')];

assert.deepEqual(
axe.commons.dom.idrefs(start, 'target'),
Expand All @@ -80,17 +76,17 @@ describe('dom.idrefs', function () {
}
);

it('should insert null if a reference is not found', function () {
it('should insert null if a reference is not found', () => {
fixture.innerHTML =
'<div aria-cats="target1 target2 target3" id="start"></div>' +
'<div id="target1"></div><div id="target2"></div>';

var start = document.getElementById('start'),
expected = [
document.getElementById('target1'),
document.getElementById('target2'),
null
];
const start = document.getElementById('start');
const expected = [
document.getElementById('target1'),
document.getElementById('target2'),
null
];

assert.deepEqual(
axe.commons.dom.idrefs(start, 'aria-cats'),
Expand All @@ -99,22 +95,24 @@ describe('dom.idrefs', function () {
);
});

it('should not fail when extra whitespace is used', function () {
it('should not fail when extra whitespace is used', () => {
fixture.innerHTML =
'<div aria-cats=" \ttarget1 \n target2 target3 \n\t" id="start"></div>' +
'<div id="target1"></div><div id="target2"></div>';

var start = document.getElementById('start'),
expected = [
document.getElementById('target1'),
document.getElementById('target2'),
null
];
const start = document.getElementById('start');
const expected = [
document.getElementById('target1'),
document.getElementById('target2'),
null
];

assert.deepEqual(
axe.commons.dom.idrefs(start, 'aria-cats'),
expected,
'Should find it!'
);
});

// virtual-node tests test throwing for non-DOM nodes and working with complete trees
});
Loading