Precise DOM morphing
morphing
typescript
dom
1import { test, expect, describe } from "vitest"
2import { morph } from "../../src/morphlex"
3import { dom } from "./utils"
4
5describe("text input", () => {
6 test("morphing a modified value with preserveChanges enabled", () => {
7 const a = dom(`<input type="text" value="a">`) as HTMLInputElement
8 const b = dom(`<input type="text" value="b">`) as HTMLInputElement
9
10 a.value = "c"
11 morph(a, b, { preserveChanges: true })
12
13 expect(a.outerHTML).toBe(`<input type="text" value="b">`)
14 expect(a.value).toBe("c")
15 })
16
17 test("morphing a modified value preserveChanges disabled", () => {
18 const a = dom(`<input type="text" value="a">`) as HTMLInputElement
19 const b = dom(`<input type="text" value="b">`) as HTMLInputElement
20
21 a.value = "c"
22 morph(a, b, { preserveChanges: false })
23
24 expect(a.outerHTML).toBe(`<input type="text" value="b">`)
25 expect(a.value).toBe("b")
26 })
27
28 test("morphing an unmodified value with preserveChanges enabled", () => {
29 const a = dom(`<input type="text" value="a">`) as HTMLInputElement
30 const b = dom(`<input type="text" value="b">`) as HTMLInputElement
31
32 morph(a, b, { preserveChanges: true })
33
34 expect(a.outerHTML).toBe(`<input type="text" value="b">`)
35 expect(a.value).toBe("b")
36 })
37
38 test("morphing a modified value across multiple preserveChanges morphs updates defaultValue", () => {
39 const input = dom(`<input type="text" value="a">`) as HTMLInputElement
40 const firstTarget = dom(`<input type="text" value="b">`) as HTMLInputElement
41 const secondTarget = dom(`<input type="text" value="c">`) as HTMLInputElement
42
43 input.value = "user one"
44 morph(input, firstTarget, { preserveChanges: true })
45
46 expect(input.value).toBe("user one")
47 expect(input.defaultValue).toBe("b")
48 expect(input.getAttribute("value")).toBe("b")
49
50 input.value = "user two"
51 morph(input, secondTarget, { preserveChanges: true })
52
53 expect(input.value).toBe("user two")
54 expect(input.defaultValue).toBe("c")
55 expect(input.getAttribute("value")).toBe("c")
56 })
57
58 test("morphing sibling inputs keeps modified value but updates the correct defaultValue", () => {
59 const from = dom(`<div><input type="text" name="n" value="a"><input type="text" name="n" value="a"></div>`) as HTMLElement
60 const to = dom(`<div><input type="text" name="n" value="b"><input type="text" name="n" value="a"></div>`) as HTMLElement
61
62 const first = from.children[0] as HTMLInputElement
63 const second = from.children[1] as HTMLInputElement
64
65 first.value = "user typed"
66 morph(from, to, { preserveChanges: true })
67
68 expect(first.value).toBe("user typed")
69 expect(first.defaultValue).toBe("b")
70 expect(first.getAttribute("value")).toBe("b")
71 expect(second.value).toBe("a")
72 expect(second.defaultValue).toBe("a")
73 expect(second.getAttribute("value")).toBe("a")
74 })
75
76 test("morphing updates default while dirty and keeps value dirty", () => {
77 const input = dom(`<input type="text" value="a">`) as HTMLInputElement
78
79 input.value = "b"
80 expect(input.value).toBe("b")
81 expect(input.defaultValue).toBe("a")
82
83 morph(input, dom(`<input type="text" value="c">`), { preserveChanges: true })
84 expect(input.value).toBe("b")
85 expect(input.defaultValue).toBe("c")
86
87 input.value = "c"
88 expect(input.value).toBe("c")
89 expect(input.defaultValue).toBe("c")
90
91 morph(input, dom(`<input type="text" value="d">`), { preserveChanges: true })
92 expect(input.value).toBe("c")
93 expect(input.defaultValue).toBe("d")
94 })
95})
96
97describe("checkbox", () => {
98 test("morphing a modified checkbox checked with preserveChanges enabled", () => {
99 const a = dom(`<input type="checkbox">`) as HTMLInputElement
100 const b = dom(`<input type="checkbox" checked>`) as HTMLInputElement
101
102 a.checked = true
103 morph(a, b, { preserveChanges: true })
104
105 expect(a.hasAttribute("checked")).toBe(true)
106 expect(a.checked).toBe(true)
107 })
108
109 test("morphing a modified checkbox checked with preserveChanges disabled", () => {
110 const a = dom(`<input type="checkbox">`) as HTMLInputElement
111 const b = dom(`<input type="checkbox" checked>`) as HTMLInputElement
112
113 a.checked = true
114 morph(a, b, { preserveChanges: false })
115
116 expect(a.hasAttribute("checked")).toBe(true)
117 expect(a.checked).toBe(true)
118 })
119
120 test("morphing an unmodified checkbox with preserveChanges enabled", () => {
121 const a = dom(`<input type="checkbox">`) as HTMLInputElement
122 const b = dom(`<input type="checkbox" checked>`) as HTMLInputElement
123
124 morph(a, b, { preserveChanges: true })
125
126 expect(a.hasAttribute("checked")).toBe(true)
127 expect(a.checked).toBe(true)
128 })
129
130 test("morphing an unmodified checkbox checked with preserveChanges enabled", () => {
131 const a = dom(`<input type="checkbox" checked>`) as HTMLInputElement
132 const b = dom(`<input type="checkbox">`) as HTMLInputElement
133
134 morph(a, b, { preserveChanges: true })
135
136 expect(a.hasAttribute("checked")).toBe(false)
137 expect(a.checked).toBe(false)
138 })
139
140 test("morphing a modified checkbox unchecked with preserveChanges enabled", () => {
141 const a = dom(`<input type="checkbox" checked>`) as HTMLInputElement
142 const b = dom(`<input type="checkbox">`) as HTMLInputElement
143
144 a.checked = false
145 morph(a, b, { preserveChanges: true })
146
147 expect(a.hasAttribute("checked")).toBe(false)
148 expect(a.checked).toBe(false)
149 })
150
151 test("morphing a modified checkbox unchecked with preserveChanges disabled", () => {
152 const a = dom(`<input type="checkbox" checked>`) as HTMLInputElement
153 const b = dom(`<input type="checkbox">`) as HTMLInputElement
154
155 a.checked = false
156 morph(a, b, { preserveChanges: false })
157
158 expect(a.hasAttribute("checked")).toBe(false)
159 expect(a.checked).toBe(false)
160 })
161})
162
163describe("select", () => {
164 test("morphing a modified select option with preserveChanges enabled", () => {
165 const a = dom(`<select><option value="a">A</option><option value="b">B</option></select>`) as HTMLSelectElement
166 const b = dom(`<select><option value="a">A</option><option value="b" selected>B</option></select>`) as HTMLSelectElement
167
168 a.value = "b"
169 morph(a, b, { preserveChanges: true })
170
171 expect(a.options[1].hasAttribute("selected")).toBe(true)
172 expect(a.value).toBe("b")
173 expect(a.options[1].selected).toBe(true)
174 })
175
176 test("morphing a modified select option with preserveChanges disabled", () => {
177 const a = dom(`<select><option value="a">A</option><option value="b">B</option></select>`) as HTMLSelectElement
178 const b = dom(`<select><option value="a">A</option><option value="b" selected>B</option></select>`) as HTMLSelectElement
179
180 a.value = "b"
181 morph(a, b, { preserveChanges: false })
182
183 expect(a.options[1].hasAttribute("selected")).toBe(true)
184 expect(a.value).toBe("b")
185 expect(a.options[1].selected).toBe(true)
186 })
187
188 test("morphing an unmodified select option with preserveChanges enabled", () => {
189 const a = dom(`<select><option value="a">A</option><option value="b">B</option></select>`) as HTMLSelectElement
190 const b = dom(`<select><option value="a">A</option><option value="b" selected>B</option></select>`) as HTMLSelectElement
191
192 morph(a, b, { preserveChanges: true })
193
194 expect(a.options[1].hasAttribute("selected")).toBe(true)
195 expect(a.value).toBe("b")
196 expect(a.options[1].selected).toBe(true)
197 })
198
199 test("morphing a modified select option back to default with preserveChanges enabled", () => {
200 const a = dom(`<select><option value="a">A</option><option value="b" selected>B</option></select>`) as HTMLSelectElement
201 const b = dom(`<select><option value="a">A</option><option value="b">B</option></select>`) as HTMLSelectElement
202
203 a.value = "a"
204 morph(a, b, { preserveChanges: true })
205
206 expect(a.options[1].hasAttribute("selected")).toBe(false)
207 expect(a.value).toBe("a")
208 expect(a.options[0].selected).toBe(true)
209 })
210
211 test("morphing a modified select option back to default with preserveChanges disabled", () => {
212 const a = dom(`<select><option value="a">A</option><option value="b" selected>B</option></select>`) as HTMLSelectElement
213 const b = dom(`<select><option value="a">A</option><option value="b">B</option></select>`) as HTMLSelectElement
214
215 a.value = "a"
216 morph(a, b, { preserveChanges: false })
217
218 expect(a.options[1].hasAttribute("selected")).toBe(false)
219 expect(a.value).toBe("a")
220 expect(a.options[0].selected).toBe(true)
221 })
222
223 test("morphing a select option with no value", () => {
224 const a = dom(
225 `
226 <select>
227 <option></option>
228 <option></option>
229 </select>
230 `,
231 )
232
233 const b = dom(
234 `
235 <select>
236 <option value="1">A</option>
237 <option value="2">B</option>
238 </select>
239 `,
240 )
241
242 morph(a, b)
243
244 expect(a.outerHTML).toBe(
245 `
246 <select>
247 <option value="1">A</option>
248 <option value="2">B</option>
249 </select>
250 `.trim(),
251 )
252 })
253})
254
255describe("textarea", () => {
256 test("morphing a modified textarea value with preserveChanges enabled", () => {
257 const a = dom(`<textarea>a</textarea>`) as HTMLTextAreaElement
258 const b = dom(`<textarea>b</textarea>`) as HTMLTextAreaElement
259
260 a.value = "c"
261 morph(a, b, { preserveChanges: true })
262
263 expect(a.textContent).toBe("b")
264 expect(a.value).toBe("c")
265 })
266
267 test("morphing a modified textarea value with preserveChanges disabled", () => {
268 const a = dom(`<textarea>a</textarea>`) as HTMLTextAreaElement
269 const b = dom(`<textarea>b</textarea>`) as HTMLTextAreaElement
270
271 a.value = "c"
272 morph(a, b, { preserveChanges: false })
273
274 expect(a.textContent).toBe("b")
275 expect(a.value).toBe("b")
276 })
277
278 test("morphing an unmodified textarea value with preserveChanges enabled", () => {
279 const a = dom(`<textarea>a</textarea>`) as HTMLTextAreaElement
280 const b = dom(`<textarea>b</textarea>`) as HTMLTextAreaElement
281
282 morph(a, b, { preserveChanges: true })
283
284 expect(a.textContent).toBe("b")
285 expect(a.value).toBe("b")
286 })
287})