Precise DOM morphing
morphing
typescript
dom
1import { test, expect } from "vitest"
2import { morph } from "../../src/morphlex"
3import { dom } from "../new/utils"
4
5test("input type mismatch triggers continue - then finds matching type", () => {
6 // This test ensures we hit the continue statement on line 564
7 // by having two input candidates where:
8 // 1. First candidate has wrong type (continue is executed)
9 // 2. Second candidate has correct type (match succeeds)
10 const a = dom(
11 `<div>
12 <input type="text" data-marker="1">
13 <input type="checkbox" data-marker="2">
14 </div>`,
15 ) as HTMLElement
16
17 const b = dom(
18 `<div>
19 <input type="checkbox" data-marker="new">
20 </div>`,
21 ) as HTMLElement
22
23 morph(a, b)
24
25 // The checkbox should be reused, text input removed
26 expect(a.children.length).toBe(1)
27 expect((a.children[0] as HTMLInputElement).type).toBe("checkbox")
28 expect((a.children[0] as HTMLInputElement).getAttribute("data-marker")).toBe("new")
29})
30
31test("input type mismatch with multiple wrong types before match", () => {
32 // Multiple candidates with wrong types, continue is executed multiple times
33 const a = dom(
34 `<div>
35 <input type="text">
36 <input type="radio">
37 <input type="number">
38 <input type="email" data-id="target">
39 </div>`,
40 ) as HTMLElement
41
42 const b = dom(
43 `<div>
44 <input type="email" data-id="new">
45 </div>`,
46 ) as HTMLElement
47
48 morph(a, b)
49
50 // Text, radio, and number inputs trigger continue, email matches
51 expect(a.children.length).toBe(1)
52 expect((a.children[0] as HTMLInputElement).type).toBe("email")
53 expect((a.children[0] as HTMLInputElement).getAttribute("data-id")).toBe("new")
54})
55
56test("input type mismatch with no matching type - all trigger continue", () => {
57 // All candidates have wrong type, continue is executed for all, no match found
58 const a = dom(
59 `<div>
60 <input type="text">
61 <input type="checkbox">
62 <input type="radio">
63 </div>`,
64 ) as HTMLElement
65
66 const b = dom(
67 `<div>
68 <input type="email">
69 </div>`,
70 ) as HTMLElement
71
72 morph(a, b)
73
74 // No type matches, so new element is created, old ones removed
75 expect(a.children.length).toBe(1)
76 expect((a.children[0] as HTMLInputElement).type).toBe("email")
77})
78
79test("input with matching type does not trigger continue", () => {
80 // When types match, the continue branch is NOT taken
81 const a = dom(
82 `<div>
83 <input type="text" data-value="old">
84 <input type="text" data-value="old2">
85 </div>`,
86 ) as HTMLElement
87
88 const b = dom(
89 `<div>
90 <input type="text" data-value="new">
91 </div>`,
92 ) as HTMLElement
93
94 const firstInput = a.children[0]
95
96 morph(a, b)
97
98 // First text input matches without triggering continue
99 expect(a.children.length).toBe(1)
100 expect(a.children[0]).toBe(firstInput)
101 expect((a.children[0] as HTMLInputElement).getAttribute("data-value")).toBe("new")
102})
103
104test("non-input elements skip the type check entirely", () => {
105 // isInputElement checks prevent non-inputs from entering the type check
106 const a = dom(
107 `<div>
108 <button data-test="1">A</button>
109 <button data-test="2">B</button>
110 </div>`,
111 ) as HTMLElement
112
113 const b = dom(
114 `<div>
115 <button data-test="new">C</button>
116 </div>`,
117 ) as HTMLElement
118
119 const firstButton = a.children[0]
120
121 morph(a, b)
122
123 // Buttons match by localName without any type checking
124 expect(a.children.length).toBe(1)
125 expect(a.children[0]).toBe(firstButton)
126 expect(a.children[0].getAttribute("data-test")).toBe("new")
127})
128
129test("mixed inputs and non-inputs in localName matching", () => {
130 // Ensure the logic handles both inputs and non-inputs correctly
131 const a = dom(
132 `<div>
133 <input type="text">
134 <button>Button</button>
135 <input type="email" class="target">
136 </div>`,
137 ) as HTMLElement
138
139 const b = dom(
140 `<div>
141 <input type="email" class="new">
142 <button>New Button</button>
143 </div>`,
144 ) as HTMLElement
145
146 morph(a, b)
147
148 // Email input and button should both be matched
149 expect(a.children.length).toBe(2)
150 expect((a.children[0] as HTMLInputElement).type).toBe("email")
151 expect(a.children[1].tagName).toBe("BUTTON")
152})