···44- Try to maintain 100% test coverage. Use `bun run test --coverage`.
55- I’m using `jj` so you can use that to look at your diff, but please don’t commit unless I ask you to.
66- Make sure you leave things in a good state. No diagnostics warnings. No type errors.
77+- We use tabs for indentation and spaces for alignment
+26-16
src/morphlex.ts
···3535 if (typeof to === "string") {
3636 const fragment = parseString(to)
37373838- if (fragment.firstChild && fragment.childNodes.length === 1) {
3838+ if (fragment.firstChild && fragment.childNodes.length === 1 && isElement(fragment.firstChild)) {
3939 to = fragment.firstChild
4040 } else {
4141 throw new Error("[Morphlex] The string was not a valid HTML element.")
···4646 if (isElementPair(pair) && isMatchingElementPair(pair)) {
4747 new Morph(options).morphChildren(pair)
4848 } else {
4949- throw new Error("[Morphlex] The nodes are not matching elements.")
4949+ throw new Error("[Morphlex] You can only do an inner morph with matching elements.")
5050 }
5151}
5252···254254255255 private morphChildNodes([from, to]: PairOfMatchingElements<Element>): void {
256256 const fromChildNodes = from.childNodes
257257- const toChildNodes = to.childNodes
257257+ const toChildNodes = Array.from(to.childNodes)
258258259259+ // Process all reference nodes
259260 for (let i = 0; i < toChildNodes.length; i++) {
260261 const fromChildNode = fromChildNodes[i]
261262 const toChildNode = toChildNodes[i]!
262263263263- if (fromChildNode && toChildNode) {
264264+ if (fromChildNode) {
264265 if (isElement(toChildNode)) {
265266 this.searchSiblingsToMorphChildElement(fromChildNode, toChildNode, from)
266267 } else {
267267- // TODO
268268+ this.morphOneToOne(fromChildNode, toChildNode)
269269+ }
270270+ } else {
271271+ // Add new node at the end
272272+ if (this.options.beforeNodeAdded?.(toChildNode) ?? true) {
273273+ moveBefore(from, toChildNode, null)
274274+ this.options.afterNodeAdded?.(toChildNode)
268275 }
269269- } else if (toChildNode) {
270270- this.appendChild(from, toChildNode)
271271- } else if (fromChildNode) {
272272- this.removeNode(fromChildNode)
276276+ }
277277+ }
278278+279279+ // Remove any excess nodes from the original
280280+ while (from.childNodes.length > toChildNodes.length) {
281281+ const lastChild = from.lastChild
282282+ if (lastChild) {
283283+ this.removeNode(lastChild)
273284 }
274285 }
275286 }
···311322 }
312323313324 if (bestMatch) {
314314- if (!(this.options.beforeNodeMorphed?.(bestMatch, to) ?? true)) return
315315- moveBefore(parent, bestMatch, from)
316316- this.options.afterNodeMorphed?.(bestMatch, to)
317317- this.morphMatchingElements([bestMatch, to] as PairOfMatchingElements<Element>)
325325+ if (bestMatch !== from) {
326326+ moveBefore(parent, bestMatch, from)
327327+ }
328328+ this.morphOneToOne(bestMatch, to)
318329 } else {
319330 this.morphOneToOne(from, to)
320331 }
···331342332343 private replaceNode(node: ChildNode, newNode: ChildNode): void {
333344 if (this.options.beforeNodeAdded?.(newNode) ?? true) {
334334- moveBefore(node.parentNode || document, node, newNode)
345345+ moveBefore(node.parentNode || document, newNode, node)
335346 this.options.afterNodeAdded?.(newNode)
347347+ this.removeNode(node)
336348 }
337337-338338- this.removeNode(node)
339349 }
340350341351 private appendChild(parent: ParentNode, newChild: ChildNode): void {
+12-9
test/morphlex-edge-cases.test.ts
···77 const div = document.createElement("div")
88 div.innerHTML = "<span>test</span>"
991010- // Empty string should throw an error because doc.body.firstChild will be null
1111- expect(() => morph(div.firstChild!, "")).toThrow("[Morphlex] The string was not a valid HTML node.")
1010+ // Empty string should remove the node (morphing to empty NodeList)
1111+ morph(div.firstChild!, "")
1212+ expect(div.firstChild).toBeNull()
1213 })
13141415 it("should throw error when morphInner receives empty string", () => {
···1617 div.innerHTML = "<span>content</span>"
17181819 // morphInner with empty string should throw an error
1919- // because parseChildNodeFromString returns null for empty body
2020- expect(() => morphInner(div, "")).toThrow("[Morphlex] The string was not a valid HTML node.")
2020+ // because parseString returns empty fragment
2121+ expect(() => morphInner(div, "")).toThrow("[Morphlex] The string was not a valid HTML element.")
2122 })
22232324 it("should work with valid HTML strings", () => {
2424- const div = document.createElement("div")
2525- div.innerHTML = "<span>old</span>"
2525+ const parent = document.createElement("div")
2626+ const span = document.createElement("span")
2727+ span.textContent = "old"
2828+ parent.appendChild(span)
26292727- expect(() => morph(div.firstChild!, "<div>new</div>")).not.toThrow()
2828- expect(div.firstChild?.nodeName).toBe("DIV")
2929- expect(div.firstChild?.textContent).toBe("new")
3030+ morph(span, "<div>new</div>")
3131+ expect(parent.firstChild?.nodeName).toBe("DIV")
3232+ expect(parent.firstChild?.textContent).toBe("new")
3033 })
3134 })
3235