Precise DOM morphing
morphing typescript dom
at main 132 lines 4.0 kB view raw
1import { describe, expect, test } from "vitest" 2import { morph } from "../../src/morphlex" 3import { dom } from "./utils" 4 5describe("active element preservation", () => { 6 test("applies focused input attribute updates immediately", () => { 7 const input = dom('<input type="text" value="hello world">') as HTMLInputElement 8 document.body.appendChild(input) 9 10 input.focus() 11 input.setSelectionRange(2, 5) 12 13 const next = dom('<input type="text" value="server value" class="new">') as HTMLInputElement 14 15 morph(input, next, { preserveChanges: false }) 16 17 expect(document.activeElement).toBe(input) 18 expect(input.value).toBe("server value") 19 expect(input.getAttribute("value")).toBe("server value") 20 expect(input.className).toBe("new") 21 22 input.remove() 23 }) 24 25 test("does not defer focused descendant updates until blur", () => { 26 const wrapper = document.createElement("div") 27 wrapper.innerHTML = '<input id="name" value="hello" class="old"><button id="next">next</button>' 28 document.body.appendChild(wrapper) 29 30 const input = wrapper.querySelector("#name") as HTMLInputElement 31 const nextButton = wrapper.querySelector("#next") as HTMLButtonElement 32 33 input.value = "user typed" 34 input.focus() 35 36 const targetWrapper = document.createElement("div") 37 targetWrapper.innerHTML = '<input id="name" value="server" class="new"><button id="next">next</button>' 38 39 morph(wrapper, targetWrapper, { preserveChanges: false }) 40 41 expect(input.value).toBe("server") 42 expect(input.defaultValue).toBe("server") 43 expect(input.className).toBe("new") 44 45 nextButton.focus() 46 47 expect(input.value).toBe("server") 48 expect(input.defaultValue).toBe("server") 49 expect(input.getAttribute("value")).toBe("server") 50 expect(input.className).toBe("new") 51 52 wrapper.remove() 53 }) 54 55 test("replaces active contenteditable element", () => { 56 const parent = document.createElement("div") 57 const from = document.createElement("div") 58 from.contentEditable = "true" 59 from.textContent = "user text" 60 parent.appendChild(from) 61 document.body.appendChild(parent) 62 63 from.focus() 64 65 const to = document.createElement("p") 66 to.textContent = "server text" 67 68 morph(from, to) 69 70 expect(parent.firstElementChild).toBe(to) 71 expect(to.textContent).toBe("server text") 72 73 parent.remove() 74 }) 75 76 test("updates focused input when preserveChanges is disabled", () => { 77 const input = dom('<input type="text" value="hello world">') as HTMLInputElement 78 document.body.appendChild(input) 79 80 input.focus() 81 82 const next = dom('<input type="text" value="server value">') as HTMLInputElement 83 84 morph(input, next, { preserveChanges: false }) 85 86 expect(input.value).toBe("server value") 87 88 input.remove() 89 }) 90 91 test("allows moving active element while reordering", () => { 92 const from = document.createElement("div") 93 from.innerHTML = '<input id="active"><p id="sibling">A</p>' 94 document.body.appendChild(from) 95 96 const active = from.querySelector("#active") as HTMLInputElement 97 active.focus() 98 99 const to = document.createElement("div") 100 to.innerHTML = '<p id="sibling">A</p><input id="active">' 101 102 morph(from, to) 103 104 expect(from.querySelector("#active")).toBe(active) 105 expect(from.firstElementChild?.id).toBe("sibling") 106 expect(from.lastElementChild?.id).toBe("active") 107 expect(document.activeElement).toBe(active) 108 109 from.remove() 110 }) 111 112 test("allows replacing an ancestor that contains the active element", () => { 113 const host = document.createElement("div") 114 const from = document.createElement("div") 115 from.innerHTML = '<input id="active" value="hello"><span>old</span>' 116 host.appendChild(from) 117 document.body.appendChild(host) 118 119 const active = from.querySelector("#active") as HTMLInputElement 120 active.focus() 121 122 const to = document.createElement("section") 123 to.innerHTML = '<input id="active" value="server"><span>new</span>' 124 125 morph(from, to, { preserveChanges: false }) 126 127 expect(host.firstElementChild?.tagName).toBe("SECTION") 128 expect((host.querySelector("#active") as HTMLInputElement).value).toBe("server") 129 130 host.remove() 131 }) 132})