@testing-library/jest-dom version: 6.9.1
node version: 24.15.0
jest version: 30.3.0
npm version: 11.12.1
Relevant code or config:
const host = document.createElement('div');
const shadowRoot = host.attachShadow({ mode: 'open' });
const input = document.createElement('input');
shadowRoot.appendChild(input);
document.body.appendChild(host);
input.focus();
expect(input).toHaveFocus(); // fails
What you did:
Focused an <input> element inside a shadow root, then asserted toHaveFocus() on it.
What happened:
expect(element).toHaveFocus()
Expected element with focus:
<input />
Received element with focus:
<div />
The <div /> reported as focused is the shadow host, not the <input>.
Reproduction:
https://github.com/julienw/jest-dom-to-have-focus-shadow-root
Run npm install && npm test. Six tests pass and one fails — the failing test is the bug.
Problem description:
When focus moves into a shadow root, document.activeElement returns the shadow host rather than the element that actually has focus. toHaveFocus checks element.ownerDocument.activeElement === element, so it can never return true for any element inside a shadow root.
The actually-focused element is reachable via shadowRoot.activeElement, which the matcher does not consult.
Suggested solution:
Traverse shadow roots when resolving the active element:
function getDeepActiveElement(doc) {
let active = doc.activeElement;
while (active?.shadowRoot?.activeElement) {
active = active.shadowRoot.activeElement;
}
return active;
}
Replacing element.ownerDocument.activeElement with getDeepActiveElement(element.ownerDocument) in the toHaveFocus implementation would fix the issue for arbitrarily nested shadow roots.
If the solution works for you I can do a PR with that.
@testing-library/jest-domversion: 6.9.1nodeversion: 24.15.0jestversion: 30.3.0npmversion: 11.12.1Relevant code or config:
What you did:
Focused an
<input>element inside a shadow root, then assertedtoHaveFocus()on it.What happened:
The
<div />reported as focused is the shadow host, not the<input>.Reproduction:
https://github.com/julienw/jest-dom-to-have-focus-shadow-root
Run
npm install && npm test. Six tests pass and one fails — the failing test is the bug.Problem description:
When focus moves into a shadow root,
document.activeElementreturns the shadow host rather than the element that actually has focus.toHaveFocuscheckselement.ownerDocument.activeElement === element, so it can never returntruefor any element inside a shadow root.The actually-focused element is reachable via
shadowRoot.activeElement, which the matcher does not consult.Suggested solution:
Traverse shadow roots when resolving the active element:
Replacing
element.ownerDocument.activeElementwithgetDeepActiveElement(element.ownerDocument)in thetoHaveFocusimplementation would fix the issue for arbitrarily nested shadow roots.If the solution works for you I can do a PR with that.