0

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.

6
  • Why not use querySelector() instead of querySelectorAll()? Commented Sep 2 at 16:52
  • @mykaf It doesn't matter for the result. If I would change to querySelector() I would just get null the second time. Commented Sep 2 at 17:09
  • I'm unable to recreate the problem; when I remove element.innerHTML = element.innerHTML;, I get results.length = 1. Commented Sep 2 at 17:15
  • It works in the browser, but doesn't work in jsdom. Sorry I missed this piece of information, will add it shortly. Commented Sep 2 at 17:20
  • I suggest providing context on why you want to do this in the first place; there may be a more straightforward solution than this. Commented Sep 2 at 19:40

1 Answer 1

0

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>

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.