Precise DOM morphing
morphing typescript dom

More svg tests

+262
+262
test/new/svg.browser.test.ts
··· 1 + import { describe, expect, test, beforeEach, afterEach } from "vitest" 2 + import { morph, morphInner } from "../../src/morphlex" 3 + import { observeMutations } from "./utils" 4 + 5 + describe("SVG morphing", () => { 6 + let container: HTMLElement 7 + 8 + beforeEach(() => { 9 + container = document.createElement("div") 10 + document.body.appendChild(container) 11 + }) 12 + 13 + afterEach(() => { 14 + document.body.removeChild(container) 15 + }) 16 + 17 + function svg(html: string): SVGElement { 18 + const tmp = document.createElement("div") 19 + tmp.innerHTML = html.trim() 20 + return tmp.firstChild as SVGElement 21 + } 22 + 23 + test("should morph SVG attributes", () => { 24 + const from = svg('<svg width="100" height="100" viewBox="0 0 100 100"></svg>') 25 + container.appendChild(from) 26 + 27 + const to = svg('<svg width="200" height="200" viewBox="0 0 200 200"></svg>') 28 + 29 + morph(from, to) 30 + 31 + expect(from.getAttribute("width")).toBe("200") 32 + expect(from.getAttribute("height")).toBe("200") 33 + expect(from.getAttribute("viewBox")).toBe("0 0 200 200") 34 + }) 35 + 36 + test("should add new SVG children", () => { 37 + const from = svg('<svg><circle cx="50" cy="50" r="40"></circle></svg>') 38 + container.appendChild(from) 39 + 40 + const to = svg(` 41 + <svg> 42 + <circle cx="50" cy="50" r="40"></circle> 43 + <rect x="10" y="10" width="30" height="30"></rect> 44 + </svg> 45 + `) 46 + 47 + const mutations = observeMutations(from, () => morph(from, to)) 48 + 49 + expect(from.children.length).toBe(2) 50 + expect(from.children[0].tagName).toBe("circle") 51 + expect(from.children[1].tagName).toBe("rect") 52 + expect(from.children[1].getAttribute("x")).toBe("10") 53 + expect(from.children[1].getAttribute("y")).toBe("10") 54 + expect(mutations.elementsAdded).toBe(1) 55 + }) 56 + 57 + test("should remove SVG children", () => { 58 + const from = svg(` 59 + <svg> 60 + <circle cx="25" cy="25" r="20"></circle> 61 + <circle cx="75" cy="75" r="20"></circle> 62 + </svg> 63 + `) 64 + container.appendChild(from) 65 + 66 + const to = svg('<svg><circle cx="25" cy="25" r="20"></circle></svg>') 67 + 68 + const mutations = observeMutations(from, () => morph(from, to)) 69 + 70 + expect(from.children.length).toBe(1) 71 + expect(from.children[0].getAttribute("cx")).toBe("25") 72 + expect(mutations.elementsRemoved).toBe(1) 73 + }) 74 + 75 + test("should morph SVG paths", () => { 76 + const from = svg('<svg><path d="M 10 10 L 90 90" stroke="black"></path></svg>') 77 + container.appendChild(from) 78 + 79 + const to = svg('<svg><path d="M 10 10 C 20 20, 40 20, 50 10" stroke="red" stroke-width="2"></path></svg>') 80 + 81 + const mutations = observeMutations(from, () => morph(from, to)) 82 + 83 + const morphedPath = from.querySelector("path") 84 + expect(morphedPath?.getAttribute("d")).toBe("M 10 10 C 20 20, 40 20, 50 10") 85 + expect(morphedPath?.getAttribute("stroke")).toBe("red") 86 + expect(morphedPath?.getAttribute("stroke-width")).toBe("2") 87 + expect(mutations.attributeChanges).toBeGreaterThan(0) 88 + }) 89 + 90 + test("should morph nested SVG groups", () => { 91 + const from = svg(` 92 + <svg> 93 + <g transform="translate(10, 10)"> 94 + <circle r="5"></circle> 95 + </g> 96 + </svg> 97 + `) 98 + container.appendChild(from) 99 + 100 + const to = svg(` 101 + <svg> 102 + <g transform="translate(20, 20) rotate(45)"> 103 + <circle r="5"></circle> 104 + <circle r="10"></circle> 105 + </g> 106 + </svg> 107 + `) 108 + 109 + const mutations = observeMutations(from, () => morph(from, to)) 110 + 111 + const morphedG = from.querySelector("g") 112 + expect(morphedG?.getAttribute("transform")).toBe("translate(20, 20) rotate(45)") 113 + expect(morphedG?.children.length).toBe(2) 114 + expect(morphedG?.children[1].getAttribute("r")).toBe("10") 115 + expect(mutations.elementsAdded).toBe(1) 116 + }) 117 + 118 + test("should morph SVG text elements", () => { 119 + const from = svg('<svg><text x="10" y="20">Hello</text></svg>') 120 + container.appendChild(from) 121 + 122 + const to = svg('<svg><text x="30" y="40" font-size="20">World</text></svg>') 123 + 124 + morph(from, to) 125 + 126 + const morphedText = from.querySelector("text") 127 + expect(morphedText?.getAttribute("x")).toBe("30") 128 + expect(morphedText?.getAttribute("y")).toBe("40") 129 + expect(morphedText?.getAttribute("font-size")).toBe("20") 130 + expect(morphedText?.textContent).toBe("World") 131 + }) 132 + 133 + test("should reorder SVG elements with IDs", () => { 134 + const from = svg(` 135 + <svg> 136 + <circle id="circle-1" cx="25"></circle> 137 + <circle id="circle-2" cx="50"></circle> 138 + <circle id="circle-3" cx="75"></circle> 139 + </svg> 140 + `) 141 + container.appendChild(from) 142 + 143 + const to = svg(` 144 + <svg> 145 + <circle id="circle-3" cx="75"></circle> 146 + <circle id="circle-1" cx="25"></circle> 147 + <circle id="circle-2" cx="50"></circle> 148 + </svg> 149 + `) 150 + 151 + morph(from, to) 152 + 153 + expect(from.children[0].id).toBe("circle-3") 154 + expect(from.children[1].id).toBe("circle-1") 155 + expect(from.children[2].id).toBe("circle-2") 156 + }) 157 + 158 + test("should morph SVG polygons and polylines", () => { 159 + const from = svg('<svg><polygon points="10,10 20,10 15,20"></polygon></svg>') 160 + container.appendChild(from) 161 + 162 + const to = svg('<svg><polyline points="10,10 20,20 30,10 40,20" fill="none" stroke="blue"></polyline></svg>') 163 + 164 + morph(from, to) 165 + 166 + expect(from.children[0].tagName).toBe("polyline") 167 + expect(from.children[0].getAttribute("points")).toBe("10,10 20,20 30,10 40,20") 168 + expect(from.children[0].getAttribute("stroke")).toBe("blue") 169 + }) 170 + 171 + test("should morph SVG with multiple shape types", () => { 172 + const from = svg(` 173 + <svg> 174 + <rect x="0"></rect> 175 + <ellipse cx="50"></ellipse> 176 + </svg> 177 + `) 178 + container.appendChild(from) 179 + 180 + const to = svg(` 181 + <svg> 182 + <circle cx="25"></circle> 183 + <line x1="0" y1="0" x2="100" y2="100"></line> 184 + <rect x="10"></rect> 185 + </svg> 186 + `) 187 + 188 + morph(from, to) 189 + 190 + expect(from.children.length).toBe(3) 191 + expect(from.children[0].tagName).toBe("circle") 192 + expect(from.children[1].tagName).toBe("line") 193 + expect(from.children[2].tagName).toBe("rect") 194 + }) 195 + 196 + test("should preserve SVG namespace when creating new elements", () => { 197 + const from = svg("<svg></svg>") 198 + container.appendChild(from) 199 + 200 + const to = svg('<svg><circle r="50"></circle></svg>') 201 + 202 + morph(from, to) 203 + 204 + const morphedCircle = from.children[0] 205 + expect(morphedCircle.namespaceURI).toBe("http://www.w3.org/2000/svg") 206 + expect(morphedCircle.tagName).toBe("circle") 207 + }) 208 + 209 + test("should morph SVG defs and use elements", () => { 210 + const from = svg(` 211 + <svg> 212 + <defs> 213 + <circle id="myCircle" r="10"></circle> 214 + </defs> 215 + </svg> 216 + `) 217 + container.appendChild(from) 218 + 219 + const to = svg(` 220 + <svg> 221 + <defs> 222 + <circle id="myCircle" r="20"></circle> 223 + </defs> 224 + <use href="#myCircle"></use> 225 + </svg> 226 + `) 227 + 228 + const mutations = observeMutations(from, () => morph(from, to)) 229 + 230 + expect(from.children.length).toBe(2) 231 + expect(from.children[0].tagName).toBe("defs") 232 + expect(from.children[1].tagName).toBe("use") 233 + const defsCircle = from.children[0].children[0] 234 + expect(defsCircle.getAttribute("r")).toBe("20") 235 + expect(mutations.elementsAdded).toBe(1) 236 + }) 237 + 238 + test("should morph SVG with class attributes", () => { 239 + const from = svg('<svg><circle class="small red"></circle></svg>') 240 + container.appendChild(from) 241 + 242 + const to = svg('<svg><circle class="large blue"></circle></svg>') 243 + 244 + morph(from, to) 245 + 246 + const morphedCircle = from.querySelector("circle") 247 + expect(morphedCircle?.getAttribute("class")).toBe("large blue") 248 + }) 249 + 250 + test("should morph inner content of SVG element", () => { 251 + const from = svg('<svg><circle r="10"></circle></svg>') 252 + container.appendChild(from) 253 + 254 + const to = svg('<svg><rect width="20" height="20"></rect></svg>') 255 + 256 + morphInner(from, to) 257 + 258 + expect(from.children.length).toBe(1) 259 + expect(from.children[0].tagName).toBe("rect") 260 + expect(from.children[0].getAttribute("width")).toBe("20") 261 + }) 262 + })