···11+import { test, expect } from "vitest"
22+import { morph } from "../../src/morphlex"
33+import { dom } from "../new/utils"
44+55+test("morphing inputs by localName with same types matches correctly", () => {
66+ // This test ensures lines 566-568 are covered (the non-continue path)
77+ // Inputs without id or name attributes fall through to localName matching
88+ const a = dom(`<form><input type="email" class="first"><input type="email" class="second"></form>`) as HTMLElement
99+ const b = dom(`<form><input type="email" placeholder="a"><input type="email" placeholder="b"></form>`) as HTMLElement
1010+1111+ const first = a.children[0] as HTMLInputElement
1212+ const second = a.children[1] as HTMLInputElement
1313+1414+ morph(a, b)
1515+1616+ // Same type inputs should be reused via localName matching
1717+ expect(a.children[0]).toBe(first)
1818+ expect(a.children[1]).toBe(second)
1919+ expect((a.children[0] as HTMLInputElement).placeholder).toBe("a")
2020+ expect((a.children[1] as HTMLInputElement).placeholder).toBe("b")
2121+})
2222+2323+test("morphing inputs with different types by localName skips mismatched types", () => {
2424+ // This test specifically targets lines 562-564 in morphlex.ts (the continue path)
2525+ // We need inputs without IDs or name attributes, so they fall through to localName matching
2626+ const a = dom(
2727+ `<div><input type="text" class="a"><input type="checkbox" class="b"><input type="text" class="c"></div>`,
2828+ ) as HTMLElement
2929+ const b = dom(
3030+ `<div><input type="checkbox" class="x"><input type="text" class="y"><input type="text" class="z"></div>`,
3131+ ) as HTMLElement
3232+3333+ morph(a, b)
3434+3535+ // The first input (text) can't match the first target (checkbox) due to type mismatch (line 563-564)
3636+ // So different elements should be created/replaced
3737+ const inputs = Array.from(a.children) as HTMLInputElement[]
3838+3939+ expect(inputs[0].type).toBe("checkbox")
4040+ expect(inputs[0].className).toBe("x")
4141+ expect(inputs[1].type).toBe("text")
4242+ expect(inputs[1].className).toBe("y")
4343+ expect(inputs[2].type).toBe("text")
4444+ expect(inputs[2].className).toBe("z")
4545+})
4646+4747+test("morphing option with selected attribute removed when matches default", () => {
4848+ // This test targets line 418 in morphlex.ts
4949+ const a = dom(
5050+ `<select multiple><option value="a" selected>A</option><option value="b" selected>B</option><option value="c">C</option></select>`,
5151+ ) as HTMLSelectElement
5252+ const b = dom(
5353+ `<select multiple><option value="a">A</option><option value="b" selected>B</option><option value="c">C</option></select>`,
5454+ ) as HTMLSelectElement
5555+5656+ // First option has selected attribute, so selected === defaultSelected (both true)
5757+ const firstOption = a.options[0]
5858+ expect(firstOption.selected).toBe(true)
5959+6060+ morph(a, b, { preserveChanges: true })
6161+6262+ // Line 418: since selected === defaultSelected, we set selected = false
6363+ expect(a.options[0].selected).toBe(false)
6464+ expect(a.options[0].hasAttribute("selected")).toBe(false)
6565+})
6666+6767+test("morphing option with selected attribute removed with preserveChanges false", () => {
6868+ // This test targets line 418 with preserveChanges: false branch
6969+ const a = dom(`<select><option value="a" selected>A</option><option value="b">B</option></select>`) as HTMLSelectElement
7070+ const b = dom(`<select><option value="a">A</option><option value="b">B</option></select>`) as HTMLSelectElement
7171+7272+ // First option has selected attribute
7373+ expect(a.options[0].selected).toBe(true)
7474+7575+ morph(a, b, { preserveChanges: false })
7676+7777+ // Line 418: with preserveChanges false, we set selected = false
7878+ expect(a.options[0].hasAttribute("selected")).toBe(false)
7979+})
···11+import { test, expect } from "vitest"
22+import { morph } from "../../src/morphlex"
33+import { dom } from "../new/utils"
44+55+test("input type mismatch triggers continue - then finds matching type", () => {
66+ // This test ensures we hit the continue statement on line 564
77+ // by having two input candidates where:
88+ // 1. First candidate has wrong type (continue is executed)
99+ // 2. Second candidate has correct type (match succeeds)
1010+ const a = dom(
1111+ `<div>
1212+ <input type="text" data-marker="1">
1313+ <input type="checkbox" data-marker="2">
1414+ </div>`,
1515+ ) as HTMLElement
1616+1717+ const b = dom(
1818+ `<div>
1919+ <input type="checkbox" data-marker="new">
2020+ </div>`,
2121+ ) as HTMLElement
2222+2323+ morph(a, b)
2424+2525+ // The checkbox should be reused, text input removed
2626+ expect(a.children.length).toBe(1)
2727+ expect((a.children[0] as HTMLInputElement).type).toBe("checkbox")
2828+ expect((a.children[0] as HTMLInputElement).getAttribute("data-marker")).toBe("new")
2929+})
3030+3131+test("input type mismatch with multiple wrong types before match", () => {
3232+ // Multiple candidates with wrong types, continue is executed multiple times
3333+ const a = dom(
3434+ `<div>
3535+ <input type="text">
3636+ <input type="radio">
3737+ <input type="number">
3838+ <input type="email" data-id="target">
3939+ </div>`,
4040+ ) as HTMLElement
4141+4242+ const b = dom(
4343+ `<div>
4444+ <input type="email" data-id="new">
4545+ </div>`,
4646+ ) as HTMLElement
4747+4848+ morph(a, b)
4949+5050+ // Text, radio, and number inputs trigger continue, email matches
5151+ expect(a.children.length).toBe(1)
5252+ expect((a.children[0] as HTMLInputElement).type).toBe("email")
5353+ expect((a.children[0] as HTMLInputElement).getAttribute("data-id")).toBe("new")
5454+})
5555+5656+test("input type mismatch with no matching type - all trigger continue", () => {
5757+ // All candidates have wrong type, continue is executed for all, no match found
5858+ const a = dom(
5959+ `<div>
6060+ <input type="text">
6161+ <input type="checkbox">
6262+ <input type="radio">
6363+ </div>`,
6464+ ) as HTMLElement
6565+6666+ const b = dom(
6767+ `<div>
6868+ <input type="email">
6969+ </div>`,
7070+ ) as HTMLElement
7171+7272+ morph(a, b)
7373+7474+ // No type matches, so new element is created, old ones removed
7575+ expect(a.children.length).toBe(1)
7676+ expect((a.children[0] as HTMLInputElement).type).toBe("email")
7777+})
7878+7979+test("input with matching type does not trigger continue", () => {
8080+ // When types match, the continue branch is NOT taken
8181+ const a = dom(
8282+ `<div>
8383+ <input type="text" data-value="old">
8484+ <input type="text" data-value="old2">
8585+ </div>`,
8686+ ) as HTMLElement
8787+8888+ const b = dom(
8989+ `<div>
9090+ <input type="text" data-value="new">
9191+ </div>`,
9292+ ) as HTMLElement
9393+9494+ const firstInput = a.children[0]
9595+9696+ morph(a, b)
9797+9898+ // First text input matches without triggering continue
9999+ expect(a.children.length).toBe(1)
100100+ expect(a.children[0]).toBe(firstInput)
101101+ expect((a.children[0] as HTMLInputElement).getAttribute("data-value")).toBe("new")
102102+})
103103+104104+test("non-input elements skip the type check entirely", () => {
105105+ // isInputElement checks prevent non-inputs from entering the type check
106106+ const a = dom(
107107+ `<div>
108108+ <button data-test="1">A</button>
109109+ <button data-test="2">B</button>
110110+ </div>`,
111111+ ) as HTMLElement
112112+113113+ const b = dom(
114114+ `<div>
115115+ <button data-test="new">C</button>
116116+ </div>`,
117117+ ) as HTMLElement
118118+119119+ const firstButton = a.children[0]
120120+121121+ morph(a, b)
122122+123123+ // Buttons match by localName without any type checking
124124+ expect(a.children.length).toBe(1)
125125+ expect(a.children[0]).toBe(firstButton)
126126+ expect(a.children[0].getAttribute("data-test")).toBe("new")
127127+})
128128+129129+test("mixed inputs and non-inputs in localName matching", () => {
130130+ // Ensure the logic handles both inputs and non-inputs correctly
131131+ const a = dom(
132132+ `<div>
133133+ <input type="text">
134134+ <button>Button</button>
135135+ <input type="email" class="target">
136136+ </div>`,
137137+ ) as HTMLElement
138138+139139+ const b = dom(
140140+ `<div>
141141+ <input type="email" class="new">
142142+ <button>New Button</button>
143143+ </div>`,
144144+ ) as HTMLElement
145145+146146+ morph(a, b)
147147+148148+ // Email input and button should both be matched
149149+ expect(a.children.length).toBe(2)
150150+ expect((a.children[0] as HTMLInputElement).type).toBe("email")
151151+ expect(a.children[1].tagName).toBe("BUTTON")
152152+})
···11+import { test, expect, describe } from "vitest"
22+import { morph } from "../../src/morphlex"
33+import { dom } from "../new/utils"
44+55+describe("input type mismatch", () => {
66+ test("morphing inputs with different types treats them as different elements", () => {
77+ const a = dom(`<div><input type="text" id="a"><input type="checkbox" id="b"></div>`) as HTMLElement
88+ const b = dom(`<div><input type="checkbox" id="a"><input type="text" id="b"></div>`) as HTMLElement
99+1010+ morph(a, b)
1111+1212+ const firstInput = a.querySelector("#a") as HTMLInputElement
1313+ const secondInput = a.querySelector("#b") as HTMLInputElement
1414+1515+ expect(firstInput.type).toBe("checkbox")
1616+ expect(secondInput.type).toBe("text")
1717+ })
1818+1919+ test("morphing inputs with same type but different order", () => {
2020+ const a = dom(`<div><input type="text" id="a" value="first"><input type="text" id="b" value="second"></div>`) as HTMLElement
2121+ const b = dom(`<div><input type="text" id="b" value="second"><input type="text" id="a" value="first"></div>`) as HTMLElement
2222+2323+ morph(a, b)
2424+2525+ const firstInput = a.firstElementChild as HTMLInputElement
2626+ const secondInput = a.lastElementChild as HTMLInputElement
2727+2828+ expect(firstInput.id).toBe("b")
2929+ expect(firstInput.value).toBe("second")
3030+ expect(secondInput.id).toBe("a")
3131+ expect(secondInput.value).toBe("first")
3232+ })
3333+3434+ test("morphing text input to number input creates new element", () => {
3535+ const a = dom(`<div><input type="text" value="hello"></div>`) as HTMLElement
3636+ const b = dom(`<div><input type="number" value="123"></div>`) as HTMLElement
3737+3838+ const originalInput = a.firstElementChild
3939+ morph(a, b)
4040+ const newInput = a.firstElementChild as HTMLInputElement
4141+4242+ // Different types should result in element replacement
4343+ expect(newInput.type).toBe("number")
4444+ expect(newInput.value).toBe("123")
4545+ })
4646+4747+ test("morphing checkbox to radio creates new element", () => {
4848+ const a = dom(`<div><input type="checkbox" checked></div>`) as HTMLElement
4949+ const b = dom(`<div><input type="radio" name="test"></div>`) as HTMLElement
5050+5151+ morph(a, b)
5252+ const newInput = a.firstElementChild as HTMLInputElement
5353+5454+ expect(newInput.type).toBe("radio")
5555+ expect(newInput.name).toBe("test")
5656+ })
5757+5858+ test("morphing inputs with same type uses localName matching", () => {
5959+ const a = dom(`<div><input type="text" value="a"><input type="text" value="b"></div>`) as HTMLElement
6060+ const b = dom(`<div><input type="text" value="x"><input type="text" value="y"></div>`) as HTMLElement
6161+6262+ const firstInput = a.children[0] as HTMLInputElement
6363+ const secondInput = a.children[1] as HTMLInputElement
6464+6565+ morph(a, b)
6666+6767+ // Lines 566-568: inputs match by localName and type, so they're reused
6868+ expect(a.children[0]).toBe(firstInput)
6969+ expect(a.children[1]).toBe(secondInput)
7070+ expect((a.children[0] as HTMLInputElement).value).toBe("x")
7171+ expect((a.children[1] as HTMLInputElement).value).toBe("y")
7272+ })
7373+7474+ test("morphing mixed inputs where some types match and some don't", () => {
7575+ const a = dom(`<div><input type="text" id="1"><input type="checkbox" id="2"><input type="text" id="3"></div>`) as HTMLElement
7676+ const b = dom(`<div><input type="text" id="a"><input type="radio" id="b"><input type="text" id="c"></div>`) as HTMLElement
7777+7878+ morph(a, b)
7979+8080+ // First and third should reuse text inputs, middle should be replaced
8181+ const inputs = Array.from(a.children) as HTMLInputElement[]
8282+ expect(inputs[0].type).toBe("text")
8383+ expect(inputs[0].id).toBe("a")
8484+ expect(inputs[1].type).toBe("radio")
8585+ expect(inputs[1].id).toBe("b")
8686+ expect(inputs[2].type).toBe("text")
8787+ expect(inputs[2].id).toBe("c")
8888+ })
8989+9090+ test("morphing inputs without IDs triggers localName matching with type check", () => {
9191+ const a = dom(`<div><input type="text" class="a"><input type="number" class="b"></div>`) as HTMLElement
9292+ const b = dom(`<div><input type="text" class="x"><input type="number" class="y"></div>`) as HTMLElement
9393+9494+ const firstInput = a.children[0] as HTMLInputElement
9595+ const secondInput = a.children[1] as HTMLInputElement
9696+9797+ morph(a, b)
9898+9999+ // Lines 566-568: same-type inputs are matched and reused via localName
100100+ expect(a.children[0]).toBe(firstInput)
101101+ expect(a.children[1]).toBe(secondInput)
102102+ expect((a.children[0] as HTMLInputElement).className).toBe("x")
103103+ expect((a.children[1] as HTMLInputElement).className).toBe("y")
104104+ })
105105+})
···11+import { test, expect } from "vitest"
22+import { morph } from "../../src/morphlex"
33+import { dom } from "../new/utils"
44+55+test("morphing inputs by localName without any matching attributes", () => {
66+ // Lines 566-568: inputs match by localName when types are the same
77+ // Remove all id, name, href, src attributes to force localName matching
88+ const a = dom(`<div><input type="text"><input type="text"></div>`) as HTMLElement
99+ const b = dom(`<div><input type="text" placeholder="first"><input type="text" placeholder="second"></div>`) as HTMLElement
1010+1111+ const firstInput = a.children[0] as HTMLInputElement
1212+ const secondInput = a.children[1] as HTMLInputElement
1313+1414+ morph(a, b)
1515+1616+ // Elements should be reused via localName matching
1717+ expect(a.children[0]).toBe(firstInput)
1818+ expect(a.children[1]).toBe(secondInput)
1919+ expect(firstInput.placeholder).toBe("first")
2020+ expect(secondInput.placeholder).toBe("second")
2121+})
2222+2323+test("morphing inputs with type mismatch skips candidate", () => {
2424+ // Line 562-564: when types don't match, continue to next candidate
2525+ const a = dom(`<div><input type="text"><input type="number"></div>`) as HTMLElement
2626+ const b = dom(`<div><input type="number"><input type="text"></div>`) as HTMLElement
2727+2828+ morph(a, b)
2929+3030+ const inputs = Array.from(a.children) as HTMLInputElement[]
3131+ expect(inputs[0].type).toBe("number")
3232+ expect(inputs[1].type).toBe("text")
3333+})
3434+3535+test("morphing textarea with modified value preserves change", () => {
3636+ // Line 190: textarea dirty flag
3737+ const a = dom(`<div><textarea>original</textarea></div>`) as HTMLElement
3838+ const b = dom(`<div><textarea>new</textarea></div>`) as HTMLElement
3939+4040+ const textarea = a.firstElementChild as HTMLTextAreaElement
4141+ textarea.value = "user input"
4242+4343+ morph(a, b, { preserveChanges: true })
4444+4545+ // User's input should be preserved
4646+ expect(textarea.value).toBe("user input")
4747+ expect(textarea.textContent).toBe("new")
4848+})
4949+5050+test("morphing buttons by localName", () => {
5151+ // Another test for lines 566-568 with different element types
5252+ const a = dom(`<div><button>A</button><button>B</button></div>`) as HTMLElement
5353+ const b = dom(`<div><button>X</button><button>Y</button></div>`) as HTMLElement
5454+5555+ const first = a.children[0]
5656+ const second = a.children[1]
5757+5858+ morph(a, b)
5959+6060+ expect(a.children[0]).toBe(first)
6161+ expect(a.children[1]).toBe(second)
6262+ expect(first.textContent).toBe("X")
6363+ expect(second.textContent).toBe("Y")
6464+})
6565+6666+test("morphing spans by localName forces lines 566-568", () => {
6767+ // Explicit test for lines 566-568: non-input elements matching by localName
6868+ // Elements must NOT be equal by isEqualNode, so give them different initial content
6969+ const a = dom(`<div><span>a</span><span>b</span><span>c</span></div>`) as HTMLElement
7070+ const b = dom(`<div><span data-id="1">x</span><span data-id="2">y</span><span data-id="3">z</span></div>`) as HTMLElement
7171+7272+ const first = a.children[0]
7373+ const second = a.children[1]
7474+ const third = a.children[2]
7575+7676+ morph(a, b)
7777+7878+ // Lines 566-568: localName matches, not inputs, so matches[i] = candidate, delete, break
7979+ expect(a.children[0]).toBe(first)
8080+ expect(a.children[1]).toBe(second)
8181+ expect(a.children[2]).toBe(third)
8282+})
8383+8484+test("morphing divs by localName", () => {
8585+ // Another explicit test for lines 566-568
8686+ // Elements must NOT be equal by isEqualNode
8787+ const a = dom(`<section><div>content1</div><div>content2</div></section>`) as HTMLElement
8888+ const b = dom(`<section><div title="a">new1</div><div title="b">new2</div></section>`) as HTMLElement
8989+9090+ const first = a.children[0]
9191+ const second = a.children[1]
9292+9393+ morph(a, b)
9494+9595+ expect(a.children[0]).toBe(first)
9696+ expect(a.children[1]).toBe(second)
9797+})
9898+9999+test("morphing elements with same tag but different attributes by localName", () => {
100100+ // Forcing lines 566-568: elements that don't match by isEqualNode, id, idSet, or heuristics
101101+ const a = dom(`<ul><li data-a="1">A</li><li data-a="2">B</li><li data-a="3">C</li></ul>`) as HTMLElement
102102+ const b = dom(`<ul><li data-b="x">X</li><li data-b="y">Y</li><li data-b="z">Z</li></ul>`) as HTMLElement
103103+104104+ const first = a.children[0]
105105+ const second = a.children[1]
106106+ const third = a.children[2]
107107+108108+ morph(a, b)
109109+110110+ // Should match by localName (li) and reuse elements
111111+ expect(a.children[0]).toBe(first)
112112+ expect(a.children[1]).toBe(second)
113113+ expect(a.children[2]).toBe(third)
114114+ expect(first.textContent).toBe("X")
115115+ expect(second.textContent).toBe("Y")
116116+ expect(third.textContent).toBe("Z")
117117+})
118118+119119+test("morphing p elements by localName", () => {
120120+ // Yet another test for lines 566-568 with paragraph elements
121121+ const a = dom(`<article><p class="old">First</p><p class="old">Second</p></article>`) as HTMLElement
122122+ const b = dom(`<article><p class="new">Changed1</p><p class="new">Changed2</p></article>`) as HTMLElement
123123+124124+ const first = a.children[0]
125125+ const second = a.children[1]
126126+127127+ morph(a, b)
128128+129129+ expect(a.children[0]).toBe(first)
130130+ expect(a.children[1]).toBe(second)
131131+ expect(first.textContent).toBe("Changed1")
132132+ expect(second.textContent).toBe("Changed2")
133133+})