ripple 0.2.68 → 0.2.69
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/compiler/phases/1-parse/index.js +131 -0
- package/src/compiler/phases/2-analyze/index.js +17 -11
- package/src/compiler/phases/3-transform/index.js +78 -31
- package/src/compiler/scope.js +403 -397
- package/src/compiler/utils.js +16 -0
- package/src/constants.js +4 -0
- package/src/runtime/array.js +33 -24
- package/src/runtime/internal/client/for.js +69 -31
- package/src/runtime/internal/client/render.js +9 -3
- package/src/runtime/internal/client/template.js +51 -5
- package/tests/__snapshots__/basic.test.ripple.snap +18 -0
- package/tests/__snapshots__/for.test.ripple.snap +78 -0
- package/tests/basic.test.ripple +15 -0
- package/tests/for.test.ripple +32 -2
- package/tests/svg.test.ripple +282 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { mount, flushSync } from 'ripple';
|
|
3
|
+
|
|
4
|
+
describe('SVG namespace handling', () => {
|
|
5
|
+
let container;
|
|
6
|
+
|
|
7
|
+
function render(component) {
|
|
8
|
+
mount(component, {
|
|
9
|
+
target: container
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
container = document.createElement('div');
|
|
15
|
+
document.body.appendChild(container);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
document.body.removeChild(container);
|
|
20
|
+
container = null;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should render static SVG elements with correct namespace', () => {
|
|
24
|
+
component App() {
|
|
25
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" stroke="currentColor">
|
|
26
|
+
<path d="m14 12 4 4 4-4" />
|
|
27
|
+
<circle cx="12" cy="12" r="4" />
|
|
28
|
+
<rect x="4" y="4" width="16" height="16" />
|
|
29
|
+
</svg>
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
render(App);
|
|
33
|
+
|
|
34
|
+
const svg = container.querySelector('svg');
|
|
35
|
+
const path = container.querySelector('path');
|
|
36
|
+
const circle = container.querySelector('circle');
|
|
37
|
+
const rect = container.querySelector('rect');
|
|
38
|
+
|
|
39
|
+
// Verify SVG namespace
|
|
40
|
+
expect(svg).toBeTruthy();
|
|
41
|
+
expect(svg.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
42
|
+
|
|
43
|
+
// Verify child elements have SVG namespace
|
|
44
|
+
expect(path.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
45
|
+
expect(circle.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
46
|
+
expect(rect.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
47
|
+
|
|
48
|
+
// Verify attributes are set correctly
|
|
49
|
+
expect(path.getAttribute('d')).toBe('m14 12 4 4 4-4');
|
|
50
|
+
expect(circle.getAttribute('cx')).toBe('12');
|
|
51
|
+
expect(rect.getAttribute('width')).toBe('16');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should render dynamic SVG paths with for loop (original issue)', () => {
|
|
55
|
+
component App() {
|
|
56
|
+
const d = ["m14 12 4 4 4-4", "M18 16V7", "m2 16 4.039-9.69a.5.5 0 0 1 .923 0L11 16", "M3.304 13h6.392"];
|
|
57
|
+
|
|
58
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" stroke="currentColor">
|
|
59
|
+
for (const pathData of d) {
|
|
60
|
+
<path d={pathData} />
|
|
61
|
+
}
|
|
62
|
+
</svg>
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
render(App);
|
|
66
|
+
|
|
67
|
+
const svg = container.querySelector('svg');
|
|
68
|
+
const paths = container.querySelectorAll('path');
|
|
69
|
+
|
|
70
|
+
// Verify SVG container
|
|
71
|
+
expect(svg).toBeTruthy();
|
|
72
|
+
expect(svg.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
73
|
+
|
|
74
|
+
// Critical test: dynamic paths should have correct SVG namespace
|
|
75
|
+
expect(paths.length).toBe(4);
|
|
76
|
+
const expectedPaths = ["m14 12 4 4 4-4", "M18 16V7", "m2 16 4.039-9.69a.5.5 0 0 1 .923 0L11 16", "M3.304 13h6.392"];
|
|
77
|
+
paths.forEach((path, i) => {
|
|
78
|
+
expect(path.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
79
|
+
expect(path.getAttribute('d')).toBe(expectedPaths[i]);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Verify paths are actually SVG elements (should have getBBox method)
|
|
83
|
+
// Note: getBBox might not work in test environment, so just check namespace
|
|
84
|
+
paths.forEach(path => {
|
|
85
|
+
expect(path.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
86
|
+
expect(path.tagName.toLowerCase()).toBe('path');
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should handle mixed static and dynamic SVG elements', () => {
|
|
91
|
+
component App() {
|
|
92
|
+
const dynamicPaths = ["M12 2L2 7v10c0 5.55 3.84 10 9 11 5.16-1 9-5.45 9-11V7l-10-5z"];
|
|
93
|
+
|
|
94
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" stroke="currentColor">
|
|
95
|
+
<circle cx="12" cy="12" r="10" />
|
|
96
|
+
for (const pathData of dynamicPaths) {
|
|
97
|
+
<path d={pathData} />
|
|
98
|
+
}
|
|
99
|
+
<rect x="4" y="4" width="16" height="16" />
|
|
100
|
+
</svg>
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
render(App);
|
|
104
|
+
|
|
105
|
+
const svg = container.querySelector('svg');
|
|
106
|
+
const circle = container.querySelector('circle');
|
|
107
|
+
const path = container.querySelector('path');
|
|
108
|
+
const rect = container.querySelector('rect');
|
|
109
|
+
|
|
110
|
+
// All elements should have SVG namespace
|
|
111
|
+
expect(svg.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
112
|
+
expect(circle.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
113
|
+
expect(path.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
114
|
+
expect(rect.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
115
|
+
|
|
116
|
+
// Verify content
|
|
117
|
+
expect(path.getAttribute('d')).toBe('M12 2L2 7v10c0 5.55 3.84 10 9 11 5.16-1 9-5.45 9-11V7l-10-5z');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should handle nested SVG groups with for loops', () => {
|
|
121
|
+
component App() {
|
|
122
|
+
const items = [
|
|
123
|
+
{ x: "10", y: "10", width: "20", height: "20" },
|
|
124
|
+
{ x: "40", y: "40", width: "20", height: "20" }
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100">
|
|
128
|
+
<g fill="blue">
|
|
129
|
+
for (const item of items) {
|
|
130
|
+
<rect x={item.x} y={item.y} width={item.width} height={item.height} />
|
|
131
|
+
}
|
|
132
|
+
</g>
|
|
133
|
+
</svg>
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
render(App);
|
|
137
|
+
|
|
138
|
+
const svg = container.querySelector('svg');
|
|
139
|
+
const g = container.querySelector('g');
|
|
140
|
+
const rects = container.querySelectorAll('rect');
|
|
141
|
+
|
|
142
|
+
// Verify namespaces
|
|
143
|
+
expect(svg.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
144
|
+
expect(g.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
145
|
+
expect(rects.length).toBe(2);
|
|
146
|
+
|
|
147
|
+
rects.forEach(rect => {
|
|
148
|
+
expect(rect.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Verify attributes
|
|
152
|
+
expect(rects[0].getAttribute('x')).toBe('10');
|
|
153
|
+
expect(rects[0].getAttribute('y')).toBe('10');
|
|
154
|
+
expect(rects[1].getAttribute('x')).toBe('40');
|
|
155
|
+
expect(rects[1].getAttribute('y')).toBe('40');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should handle SVG class attributes correctly', () => {
|
|
159
|
+
component App() {
|
|
160
|
+
let className = 'svg-element';
|
|
161
|
+
const paths = ['M10 10 L20 20'];
|
|
162
|
+
|
|
163
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" class={className}>
|
|
164
|
+
for (const pathData of paths) {
|
|
165
|
+
<path d={pathData} class="dynamic-path" />
|
|
166
|
+
}
|
|
167
|
+
<circle cx="50" cy="50" r="10" class={className} />
|
|
168
|
+
</svg>
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
render(App);
|
|
172
|
+
|
|
173
|
+
const svg = container.querySelector('svg');
|
|
174
|
+
const path = container.querySelector('path');
|
|
175
|
+
const circle = container.querySelector('circle');
|
|
176
|
+
|
|
177
|
+
// Verify namespaces
|
|
178
|
+
expect(svg.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
179
|
+
expect(path.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
180
|
+
expect(circle.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
181
|
+
|
|
182
|
+
// Verify class attributes work with SVG elements
|
|
183
|
+
expect(svg.getAttribute('class')).toBe('svg-element');
|
|
184
|
+
expect(path.getAttribute('class')).toBe('dynamic-path');
|
|
185
|
+
expect(circle.getAttribute('class')).toBe('svg-element');
|
|
186
|
+
|
|
187
|
+
// SVG elements should NOT have className property set
|
|
188
|
+
expect(svg.className).not.toBe('svg-element'); // className is different for SVG
|
|
189
|
+
expect(path.className).not.toBe('dynamic-path');
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('should handle namespace transitions with foreignObject', () => {
|
|
193
|
+
component App() {
|
|
194
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
|
|
195
|
+
<rect x="10" y="10" width="50" height="50" fill="red" />
|
|
196
|
+
<foreignObject x="100" y="100" width="80" height="80">
|
|
197
|
+
<div class="html-content">{'HTML inside SVG'}</div>
|
|
198
|
+
</foreignObject>
|
|
199
|
+
<circle cx="50" cy="150" r="20" fill="blue" />
|
|
200
|
+
</svg>
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
render(App);
|
|
204
|
+
|
|
205
|
+
const svg = container.querySelector('svg');
|
|
206
|
+
const rect = container.querySelector('rect');
|
|
207
|
+
const foreignObject = container.querySelector('foreignObject');
|
|
208
|
+
const div = container.querySelector('div');
|
|
209
|
+
const circle = container.querySelector('circle');
|
|
210
|
+
|
|
211
|
+
// SVG elements should have SVG namespace
|
|
212
|
+
expect(svg.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
213
|
+
expect(rect.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
214
|
+
expect(foreignObject.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
215
|
+
expect(circle.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
216
|
+
|
|
217
|
+
// HTML content inside foreignObject should have HTML namespace
|
|
218
|
+
expect(div.namespaceURI).toBe('http://www.w3.org/1999/xhtml');
|
|
219
|
+
expect(div.textContent).toBe('HTML inside SVG');
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('should compare static vs dynamic SVG rendering (original problem case)', () => {
|
|
223
|
+
component App() {
|
|
224
|
+
const d = ["m14 12 4 4 4-4", "M18 16V7", "m2 16 4.039-9.69a.5.5 0 0 1 .923 0L11 16", "M3.304 13h6.392"];
|
|
225
|
+
|
|
226
|
+
<div class="container">
|
|
227
|
+
{/* Dynamic SVG - the original problem case */}
|
|
228
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" stroke="currentColor" class="dynamic-svg">
|
|
229
|
+
for (const path of d) {
|
|
230
|
+
<path d={path} />
|
|
231
|
+
}
|
|
232
|
+
</svg>
|
|
233
|
+
|
|
234
|
+
{/* Static SVG - always worked */}
|
|
235
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" stroke="currentColor" class="static-svg">
|
|
236
|
+
<path d="m14 12 4 4 4-4"></path>
|
|
237
|
+
<path d="M18 16V7"></path>
|
|
238
|
+
<path d="m2 16 4.039-9.69a.5.5 0 0 1 .923 0L11 16"></path>
|
|
239
|
+
<path d="M3.304 13h6.392"></path>
|
|
240
|
+
</svg>
|
|
241
|
+
</div>
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
render(App);
|
|
245
|
+
|
|
246
|
+
const dynamicSvg = container.querySelector('.dynamic-svg');
|
|
247
|
+
const staticSvg = container.querySelector('.static-svg');
|
|
248
|
+
const dynamicPaths = dynamicSvg.querySelectorAll('path');
|
|
249
|
+
const staticPaths = staticSvg.querySelectorAll('path');
|
|
250
|
+
|
|
251
|
+
// Both SVGs should have correct namespace
|
|
252
|
+
expect(dynamicSvg.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
253
|
+
expect(staticSvg.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
254
|
+
|
|
255
|
+
// Both should have same number of paths
|
|
256
|
+
expect(dynamicPaths.length).toBe(4);
|
|
257
|
+
expect(staticPaths.length).toBe(4);
|
|
258
|
+
|
|
259
|
+
// All paths should have SVG namespace
|
|
260
|
+
dynamicPaths.forEach(path => {
|
|
261
|
+
expect(path.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
262
|
+
});
|
|
263
|
+
staticPaths.forEach(path => {
|
|
264
|
+
expect(path.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// Dynamic and static should have identical path data
|
|
268
|
+
dynamicPaths.forEach((path, i) => {
|
|
269
|
+
expect(path.getAttribute('d')).toBe(staticPaths[i].getAttribute('d'));
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// Critical test: all paths should be proper SVG elements
|
|
273
|
+
dynamicPaths.forEach(path => {
|
|
274
|
+
expect(path.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
275
|
+
expect(path.tagName.toLowerCase()).toBe('path');
|
|
276
|
+
});
|
|
277
|
+
staticPaths.forEach(path => {
|
|
278
|
+
expect(path.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
279
|
+
expect(path.tagName.toLowerCase()).toBe('path');
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
});
|