Skip to content

Fix TypeError when morphing a text or comment node via outerHTML - #155

Open
lizarusi wants to merge 2 commits into
bigskysoftware:mainfrom
lizarusi:fix-non-element-oldnode
Open

Fix TypeError when morphing a text or comment node via outerHTML#155
lizarusi wants to merge 2 commits into
bigskysoftware:mainfrom
lizarusi:fix-non-element-oldnode

Conversation

@lizarusi

@lizarusi lizarusi commented Aug 13, 2026

Copy link
Copy Markdown

Hi!

While using idiomorph at Walnut, we hit a bug that we've been carrying as a local patch — contributing the fix upstream.

The bug

Morphing a text or comment node with morphStyle: "outerHTML" throws:

TypeError: root.querySelectorAll is not a function
    at findIdElements (src/idiomorph.js:1088)
    at createIdMaps (src/idiomorph.js:1143)
    at createMorphContext (src/idiomorph.js:1003)
    at Object.morph (src/idiomorph.js:159)

Repro — e.g. replacing a comment placeholder with rendered content:

const parent = document.createElement("div");
parent.innerHTML = "<p>Before</p><!-- placeholder --><p>After</p>";
Idiomorph.morph(parent.childNodes[1], "<button>Bar</button>", {
  morphStyle: "outerHTML",
}); // 💥 TypeError: root.querySelectorAll is not a function

morph() passes the raw oldNode into createIdMapsfindIdElements, which calls root.querySelectorAll("[id]") — but text and comment nodes don't have querySelectorAll. We hit this in production at Walnut (morphing captured DOM that includes bare text/comment nodes) and have been carrying this fix as a local patch since 0.7.2.

The fix

Guard the call with optional chaining — the exact pattern this function already uses one line below for getAttribute ("root could be a document fragment which doesn't have getAttribute"). With the guard in place the rest of the algorithm handles non-element nodes correctly: the new tests show a text node and a comment node being morphed into the expected content, with siblings preserved.

First commit adds the failing tests, second commit makes them pass. npm run typecheck, npm run format:check, and the full suite pass, with coverage at 100%.

Morphing a text or comment node with morphStyle: "outerHTML" throws
"TypeError: root.querySelectorAll is not a function", because morph()
passes the raw oldNode into createIdMaps -> findIdElements. Guarding
with optional chaining (same pattern as the getAttribute guard below)
lets the morph proceed; the rest of the algorithm already handles
non-element nodes correctly.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant