nodejs: 24.7
jsdom: 26.1.0
Basically, I want to create a selector, that would exclude nested elements - it should find only elements at the first level of nesting. I have this:
it('Test selector non-nested', () => {
const mainAttribute = 'data-main';
const itemAttribute = 'data-item';
const sideAttribute = 'data-side';
document.body.innerHTML = `
<div>
<div ${mainAttribute}>
<div ${itemAttribute}>
<div ${sideAttribute}>
<div ${mainAttribute}>
<div ${itemAttribute}>
<div ${sideAttribute}></div>
</div>
</div>
</div>
</div>
</div>
</div>
`;
const result = [];
const foundElements = document.querySelectorAll(`[${mainAttribute}]`);
for (const element of foundElements) {
result.push(element);
}
for (const element of result) {
const selector = `:scope [${sideAttribute}]:not(:scope [${itemAttribute}] [${itemAttribute}] [${sideAttribute}])`;
element.innerHTML = element.innerHTML; // It makes it work!
const results = element.querySelectorAll(selector);
expect(results.length).toEqual(1);
}
});
As you can see, I want to find elements having sideAttribute
, but only in the top element having the itemAttribute
. It means that in this case I want to have 1 result for both iterations of the loop.
It doesn't work UNLESS I will throw element.innerHTML = element.innerHTML;
in there, then it magically starts working. What's going on here?
The problem happens in jsdom, but not in a browser. I created an issue ticket here: https://github.com/jsdom/jsdom/issues/3924 but maybe someone will find a workaround for that.
You could use a filter to check if there are any parents with that attribute:
const divs = Array.from(document.querySelectorAll('[data-main]'));
const topLevelDivs = divs.filter(d => d.parentNode.closest('[data-main]') === null); // you would need to use parentNode here so it doesn't return itself
console.log(topLevelDivs.length)
<div data-main="main">
<div data-item="item">
<div data-side="side">
<div data-main="main">
<div data-item="item">
<div data-side="side"></div>
</div>
</div>
</div>
</div>
</div>
<div data-main="main">
<div data-item="item">
<div data-side="side">
<div data-main="main">
<div data-item="item">
<div data-side="side"></div>
</div>
</div>
</div>
</div>
</div>