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