diff --git a/src/idiomorph.js b/src/idiomorph.js index 257c040..5a619c8 100644 --- a/src/idiomorph.js +++ b/src/idiomorph.js @@ -368,8 +368,14 @@ var Idiomorph = (function () { if (ctx.callbacks.beforeNodeAdded(newChild) === false) return null; if (ctx.idMap.has(newChild)) { // node has children with ids with possible state so create a dummy elt of same type and apply full morph algorithm - const newEmptyChild = document.createElement( - /** @type {Element} */ (newChild).tagName, + // Recreate in the node's OWN namespace (keyed on localName, not the + // uppercase-for-HTML tagName — createElementNS does NOT case-normalize). + // Plain createElement always makes an HTML-namespaced element, so a rebuilt + // inline loses the SVG namespace and setAttribute folds case-sensitive + // names (viewBox -> viewbox), rendering the graphic blank. + const newEmptyChild = document.createElementNS( + /** @type {Element} */ (newChild).namespaceURI, + /** @type {Element} */ (newChild).localName, ); oldParent.insertBefore(newEmptyChild, insertionPoint); morphNode(newEmptyChild, newChild, ctx); diff --git a/test/core.js b/test/core.js index 5e10210..a6a04ad 100644 --- a/test/core.js +++ b/test/core.js @@ -607,3 +607,44 @@ describe("Core morphing tests", function () { initial.outerHTML.should.equal("Bar"); }); }); + +describe("Foreign (SVG) namespace", function () { + setup(); + + // This is a real-browser test (Mocha `test/index.html` / web-test-runner). It + // asserts the intended invariant: an id-bearing inline keeps its SVG + // namespace and case-sensitive `viewBox` across a morph. + // + // Honesty note: on its own this test does NOT discriminate the fix in the + // headless suites. Verified empirically in web-test-runner (headless Chrome) + // and Playwright WebKit: it passes with AND without the fix, because in those + // engines the id-bearing is id-matched and *moved* (or cloned via + // importNode) rather than rebuilt through createNode's dummy -- only the HTML + // wrapper takes the dummy path, which is harmlessly HTML-namespaced anyway. + // The createElement -> HTML-namespace corruption this guards against was + // observed in a production macOS WKWebView app. Keep it in the browser suite + // so any engine that DOES route foreign content through the dummy is covered. + it("recreates an id-bearing inline SVG in the SVG namespace (createNode)", function () { + // A subtree carrying a persistent id takes createNode's id-preserving branch, + // which used document.createElement(tagName) -> HTML namespace. A rebuilt + // then loses its namespace and setAttribute folds viewBox -> viewbox. + let initial = make( + '
' + + '
', + ); + // Wrap the id-bearing in a fresh
so its ancestor must be + // created, which re-anchors the itself to be rebuilt via createNode. + Idiomorph.morph( + initial, + '
' + + '
', + { morphStyle: "outerHTML" }, + ); + + let svg = initial.querySelector("svg"); + svg.namespaceURI.should.equal("http://www.w3.org/2000/svg"); + svg.getAttribute("viewBox").should.equal("0 0 32 32"); + // And HTML wrappers keep a lowercase localName (localName, not tagName). + initial.querySelector("section").localName.should.equal("section"); + }); +});