@canvasengine/compiler 2.0.0-beta.3 → 2.0.0-beta.31
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/dist/index.d.ts +35 -2
- package/dist/index.js +48 -7
- package/dist/index.js.map +1 -1
- package/grammar.pegjs +736 -55
- package/index.ts +96 -9
- package/package.json +2 -2
- package/tests/compiler.spec.ts +838 -25
package/tests/compiler.spec.ts
CHANGED
|
@@ -51,16 +51,273 @@ describe("Compiler", () => {
|
|
|
51
51
|
expect(output).toBe(`h(Canvas)`);
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
+
describe("Dot notation", () => {
|
|
55
|
+
test("should compile component with dot notation", () => {
|
|
56
|
+
const input = `<MyComp.test />`;
|
|
57
|
+
const output = parser.parse(input);
|
|
58
|
+
expect(output).toBe(`h(MyComp.test)`);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("object function call", () => {
|
|
62
|
+
const input = `<MyComp.test() />`;
|
|
63
|
+
const output = parser.parse(input);
|
|
64
|
+
expect(output).toBe(`h(MyComp.test())`);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("function call with return object", () => {
|
|
68
|
+
const input = `<MyComp().test />`;
|
|
69
|
+
const output = parser.parse(input);
|
|
70
|
+
expect(output).toBe(`h(MyComp().test)`);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("function call with return object and params", () => {
|
|
74
|
+
const input = `<MyComp().test(x, y) />`;
|
|
75
|
+
const output = parser.parse(input);
|
|
76
|
+
expect(output).toBe(`h(MyComp().test(x, y))`);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("function call and params with return object and", () => {
|
|
80
|
+
const input = `<MyComp(x, y).test />`;
|
|
81
|
+
const output = parser.parse(input);
|
|
82
|
+
expect(output).toBe(`h(MyComp(x, y).test)`);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("function call", () => {
|
|
86
|
+
const input = `<MyComp() />`;
|
|
87
|
+
const output = parser.parse(input);
|
|
88
|
+
expect(output).toBe(`h(MyComp())`);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("function call and params", () => {
|
|
92
|
+
const input = `<MyComp(x, y) />`;
|
|
93
|
+
const output = parser.parse(input);
|
|
94
|
+
expect(output).toBe(`h(MyComp(x, y))`);
|
|
95
|
+
});
|
|
96
|
+
})
|
|
97
|
+
|
|
54
98
|
test("should compile component with dynamic attribute", () => {
|
|
55
99
|
const input = `<Canvas width={x} />`;
|
|
56
100
|
const output = parser.parse(input);
|
|
57
101
|
expect(output).toBe(`h(Canvas, { width: x })`);
|
|
58
102
|
});
|
|
59
103
|
|
|
104
|
+
test("should compile component with spread operator", () => {
|
|
105
|
+
const input = `<Canvas ...obj />`;
|
|
106
|
+
const output = parser.parse(input);
|
|
107
|
+
expect(output).toBe(`h(Canvas, obj)`);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("should compile component with spread operator object", () => {
|
|
111
|
+
const input = `<Canvas ...obj.prop />`;
|
|
112
|
+
const output = parser.parse(input);
|
|
113
|
+
expect(output).toBe(`h(Canvas, obj.prop)`);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("should compile component with spread operator function", () => {
|
|
117
|
+
const input = `<Canvas ...fn() />`;
|
|
118
|
+
const output = parser.parse(input);
|
|
119
|
+
expect(output).toBe(`h(Canvas, fn())`);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("should compile component with spread operator function and params", () => {
|
|
123
|
+
const input = `<Canvas ...fn(x, y) />`;
|
|
124
|
+
const output = parser.parse(input);
|
|
125
|
+
expect(output).toBe(`h(Canvas, fn(x, y))`);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("should compile component with dynamic attribute but is not a signal", () => {
|
|
129
|
+
const input = `<Canvas width={20} />`;
|
|
130
|
+
const output = parser.parse(input);
|
|
131
|
+
expect(output).toBe(`h(Canvas, { width: 20 })`);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("should compile if/else condition", () => {
|
|
135
|
+
const input = `
|
|
136
|
+
@if (sprite) {
|
|
137
|
+
<Sprite />
|
|
138
|
+
}
|
|
139
|
+
@else {
|
|
140
|
+
<Text text="No sprite" />
|
|
141
|
+
}
|
|
142
|
+
`;
|
|
143
|
+
const output = parser.parse(input);
|
|
144
|
+
expect(output).toBe(`cond(sprite, () => h(Sprite), () => h(Text, { text: 'No sprite' }))`);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("should compile if/else if condition", () => {
|
|
148
|
+
const input = `
|
|
149
|
+
@if (score >= 90) {
|
|
150
|
+
<Text text="A+" />
|
|
151
|
+
}
|
|
152
|
+
@else if (score >= 80) {
|
|
153
|
+
<Text text="A" />
|
|
154
|
+
}
|
|
155
|
+
`;
|
|
156
|
+
const output = parser.parse(input);
|
|
157
|
+
expect(output).toBe(`cond(computed(() => score() >= 90), () => h(Text, { text: 'A+' }), [computed(() => score() >= 80), () => h(Text, { text: 'A' })])`);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("should compile if/else if/else condition", () => {
|
|
161
|
+
const input = `
|
|
162
|
+
@if (score >= 90) {
|
|
163
|
+
<Text text="A+" />
|
|
164
|
+
}
|
|
165
|
+
@else if (score >= 80) {
|
|
166
|
+
<Text text="A" />
|
|
167
|
+
}
|
|
168
|
+
@else {
|
|
169
|
+
<Text text="F" />
|
|
170
|
+
}
|
|
171
|
+
`;
|
|
172
|
+
const output = parser.parse(input);
|
|
173
|
+
expect(output).toBe(`cond(computed(() => score() >= 90), () => h(Text, { text: 'A+' }), [computed(() => score() >= 80), () => h(Text, { text: 'A' })], () => h(Text, { text: 'F' }))`);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test("should compile if/else if/else condition within canvas", () => {
|
|
177
|
+
const input = `<Canvas>
|
|
178
|
+
<Container>
|
|
179
|
+
@if (score >= 90) {
|
|
180
|
+
<Text text="Grade: A+" x={100} y={100} color="gold" size={24} />
|
|
181
|
+
<Text text="Excellent work!" x={100} y={130} color="gold" size={16} />
|
|
182
|
+
}
|
|
183
|
+
</Container>
|
|
184
|
+
</Canvas>
|
|
185
|
+
`;
|
|
186
|
+
|
|
187
|
+
const output = parser.parse(input);
|
|
188
|
+
expect(output).toBe(`h(Canvas, null, h(Container, null, cond(computed(() => score() >= 90), () => [h(Text, { text: 'Grade: A+', x: 100, y: 100, color: 'gold', size: 24 }), h(Text, { text: 'Excellent work!', x: 100, y: 130, color: 'gold', size: 16 })])))`);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test("should compile multiple else if conditions", () => {
|
|
192
|
+
const input = `
|
|
193
|
+
@if (score >= 90) {
|
|
194
|
+
<Text text="A+" />
|
|
195
|
+
}
|
|
196
|
+
@else if (score >= 80) {
|
|
197
|
+
<Text text="A" />
|
|
198
|
+
}
|
|
199
|
+
@else if (score >= 70) {
|
|
200
|
+
<Text text="B" />
|
|
201
|
+
}
|
|
202
|
+
@else if (score >= 60) {
|
|
203
|
+
<Text text="C" />
|
|
204
|
+
}
|
|
205
|
+
@else {
|
|
206
|
+
<Text text="F" />
|
|
207
|
+
}
|
|
208
|
+
`;
|
|
209
|
+
const output = parser.parse(input);
|
|
210
|
+
expect(output).toBe(`cond(computed(() => score() >= 90), () => h(Text, { text: 'A+' }), [computed(() => score() >= 80), () => h(Text, { text: 'A' })], [computed(() => score() >= 70), () => h(Text, { text: 'B' })], [computed(() => score() >= 60), () => h(Text, { text: 'C' })], () => h(Text, { text: 'F' }))`);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test("should compile if/else with multiple elements", () => {
|
|
214
|
+
const input = `
|
|
215
|
+
@if (user.role === 'admin') {
|
|
216
|
+
<Text text="Admin Panel" />
|
|
217
|
+
<Text text="Settings" />
|
|
218
|
+
}
|
|
219
|
+
@else {
|
|
220
|
+
<Text text="Please log in" />
|
|
221
|
+
}
|
|
222
|
+
`;
|
|
223
|
+
const output = parser.parse(input);
|
|
224
|
+
expect(output).toBe(`cond(computed(() => user().role() === 'admin'), () => [h(Text, { text: 'Admin Panel' }), h(Text, { text: 'Settings' })], () => h(Text, { text: 'Please log in' }))`);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test("should compile nested if/else conditions", () => {
|
|
228
|
+
const input = `
|
|
229
|
+
@if (user) {
|
|
230
|
+
@if (user.isActive) {
|
|
231
|
+
<Text text="Active user" />
|
|
232
|
+
}
|
|
233
|
+
@else {
|
|
234
|
+
<Text text="Inactive user" />
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
@else {
|
|
238
|
+
<Text text="No user" />
|
|
239
|
+
}
|
|
240
|
+
`;
|
|
241
|
+
const output = parser.parse(input);
|
|
242
|
+
expect(output).toBe(`cond(user, () => cond(user.isActive, () => h(Text, { text: 'Active user' }), () => h(Text, { text: 'Inactive user' })), () => h(Text, { text: 'No user' }))`);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test("should compile if/else if with simple conditions", () => {
|
|
246
|
+
const input = `
|
|
247
|
+
@if (theme === 'dark') {
|
|
248
|
+
<Text text="Dark mode" />
|
|
249
|
+
}
|
|
250
|
+
@else if (theme === 'light') {
|
|
251
|
+
<Text text="Light mode" />
|
|
252
|
+
}
|
|
253
|
+
@else {
|
|
254
|
+
<Text text="Auto mode" />
|
|
255
|
+
}
|
|
256
|
+
`;
|
|
257
|
+
const output = parser.parse(input);
|
|
258
|
+
expect(output).toBe(`cond(computed(() => theme() === 'dark'), () => h(Text, { text: 'Dark mode' }), [computed(() => theme() === 'light'), () => h(Text, { text: 'Light mode' })], () => h(Text, { text: 'Auto mode' }))`);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test("should compile if/else with function conditions", () => {
|
|
262
|
+
const input = `
|
|
263
|
+
@if (isVisible()) {
|
|
264
|
+
<Sprite />
|
|
265
|
+
}
|
|
266
|
+
@else {
|
|
267
|
+
<Text text="Hidden" />
|
|
268
|
+
}
|
|
269
|
+
`;
|
|
270
|
+
const output = parser.parse(input);
|
|
271
|
+
expect(output).toBe(`cond(isVisible(), () => h(Sprite), () => h(Text, { text: 'Hidden' }))`);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test("should compile if/else if with object property conditions", () => {
|
|
275
|
+
const input = `
|
|
276
|
+
@if (sprite.visible) {
|
|
277
|
+
<Sprite />
|
|
278
|
+
}
|
|
279
|
+
@else if (sprite.loading) {
|
|
280
|
+
<Text text="Loading..." />
|
|
281
|
+
}
|
|
282
|
+
@else {
|
|
283
|
+
<Text text="Not available" />
|
|
284
|
+
}
|
|
285
|
+
`;
|
|
286
|
+
const output = parser.parse(input);
|
|
287
|
+
expect(output).toBe(`cond(sprite.visible, () => h(Sprite), [sprite.loading, () => h(Text, { text: 'Loading...' })], () => h(Text, { text: 'Not available' }))`);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test("should compile component with templating string", () => {
|
|
291
|
+
const input = `<Canvas width={\`direction: \${direction}\`} />`;
|
|
292
|
+
const output = parser.parse(input);
|
|
293
|
+
expect(output).toBe(`h(Canvas, { width: \`direction: \${direction()}\` })`);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test("should compile component with templating string with @ (literal)", () => {
|
|
297
|
+
const input = `<Canvas width={\`direction: \${@direction}\`} />`;
|
|
298
|
+
const output = parser.parse(input);
|
|
299
|
+
expect(output).toBe(`h(Canvas, { width: \`direction: \${direction}\` })`);
|
|
300
|
+
});
|
|
301
|
+
|
|
60
302
|
test("should compile component with object attribute", () => {
|
|
61
303
|
const input = `<Canvas width={ {x: 10, y: 20} } />`;
|
|
62
304
|
const output = parser.parse(input);
|
|
63
|
-
expect(output).toBe(`h(Canvas, { width:
|
|
305
|
+
expect(output).toBe(`h(Canvas, { width: { x: 10, y: 20 } })`);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
test("should compile component with complex object attribute", () => {
|
|
309
|
+
const input = `<Sprite
|
|
310
|
+
sheet={{
|
|
311
|
+
definition,
|
|
312
|
+
playing: "stand",
|
|
313
|
+
params: {
|
|
314
|
+
direction: "right"
|
|
315
|
+
},
|
|
316
|
+
onFinish
|
|
317
|
+
}}
|
|
318
|
+
/>`;
|
|
319
|
+
const output = parser.parse(input);
|
|
320
|
+
expect(output).toBe(`h(Sprite, { sheet: { definition, playing: "stand", params: { direction: "right" }, onFinish } })`);
|
|
64
321
|
});
|
|
65
322
|
|
|
66
323
|
test("should compile component with deep object attribute", () => {
|
|
@@ -69,6 +326,42 @@ describe("Compiler", () => {
|
|
|
69
326
|
expect(output).toBe(`h(Canvas, { width: computed(() => deep().value()) })`);
|
|
70
327
|
});
|
|
71
328
|
|
|
329
|
+
test('should compile component with deep object attribute', () => {
|
|
330
|
+
const input = `<Button
|
|
331
|
+
style={{
|
|
332
|
+
backgroundColor: {
|
|
333
|
+
normal: "#6c757d",
|
|
334
|
+
hover: "#5a6268",
|
|
335
|
+
pressed: "#545b62"
|
|
336
|
+
},
|
|
337
|
+
text: {
|
|
338
|
+
fontSize: 16,
|
|
339
|
+
color: "#ffffff"
|
|
340
|
+
}
|
|
341
|
+
}}
|
|
342
|
+
/>`;
|
|
343
|
+
const output = parser.parse(input);
|
|
344
|
+
expect(output).toBe(`h(Button, { style: { backgroundColor: { normal: "#6c757d", hover: "#5a6268", pressed: "#545b62" }, text: { fontSize: 16, color: "#ffffff" } } })`);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
test('should compile component with deep object attribute and shorthand', () => {
|
|
348
|
+
const input = `<Canvas>
|
|
349
|
+
<Container>
|
|
350
|
+
<Sprite x y sheet={{
|
|
351
|
+
definition,
|
|
352
|
+
playing: animationPlaying,
|
|
353
|
+
params: {
|
|
354
|
+
direction
|
|
355
|
+
}
|
|
356
|
+
}}
|
|
357
|
+
controls />
|
|
358
|
+
</Container>
|
|
359
|
+
</Canvas>
|
|
360
|
+
`;
|
|
361
|
+
const output = parser.parse(input);
|
|
362
|
+
expect(output).toBe(`h(Canvas, null, h(Container, null, h(Sprite, { x, y, sheet: { definition, playing: animationPlaying, params: { direction } }, controls })))`);
|
|
363
|
+
});
|
|
364
|
+
|
|
72
365
|
test("should compile component with deep object attribute but not transform to signal", () => {
|
|
73
366
|
const input = `<Canvas width={@deep.value} />`;
|
|
74
367
|
const output = parser.parse(input);
|
|
@@ -78,25 +371,25 @@ describe("Compiler", () => {
|
|
|
78
371
|
test("should compile component with deep object attribute but not all transform to signal", () => {
|
|
79
372
|
const input = `<Canvas width={@deep.@value} />`;
|
|
80
373
|
const output = parser.parse(input);
|
|
81
|
-
expect(output).toBe(`h(Canvas, { width:
|
|
374
|
+
expect(output).toBe(`h(Canvas, { width: deep.value })`);
|
|
82
375
|
});
|
|
83
376
|
|
|
84
377
|
test("should compile component with dynamic object attribute", () => {
|
|
85
378
|
const input = `<Canvas width={ {x: x, y: 20} } />`;
|
|
86
379
|
const output = parser.parse(input);
|
|
87
|
-
expect(output).toBe(`h(Canvas, { width:
|
|
380
|
+
expect(output).toBe(`h(Canvas, { width: { x: x, y: 20 } })`);
|
|
88
381
|
});
|
|
89
382
|
|
|
90
383
|
test("should compile component with array attribute", () => {
|
|
91
384
|
const input = `<Canvas width={ [10, 20] } />`;
|
|
92
385
|
const output = parser.parse(input);
|
|
93
|
-
expect(output).toBe(`h(Canvas, { width:
|
|
386
|
+
expect(output).toBe(`h(Canvas, { width: [10, 20] })`);
|
|
94
387
|
});
|
|
95
388
|
|
|
96
389
|
test("should compile component with dynamic array attribute", () => {
|
|
97
390
|
const input = `<Canvas width={ [x, 20] } />`;
|
|
98
391
|
const output = parser.parse(input);
|
|
99
|
-
expect(output).toBe(`h(Canvas, { width:
|
|
392
|
+
expect(output).toBe(`h(Canvas, { width: [x, 20] })`);
|
|
100
393
|
});
|
|
101
394
|
|
|
102
395
|
test("should compile component with standalone dynamic attribute", () => {
|
|
@@ -117,18 +410,24 @@ describe("Compiler", () => {
|
|
|
117
410
|
expect(output).toBe(`h(Canvas, { width: computed(() => x() * 2 * y()) })`);
|
|
118
411
|
});
|
|
119
412
|
|
|
120
|
-
test("should compile component with static numeric attribute", () => {
|
|
121
|
-
const input = `<Canvas width="10" />`;
|
|
122
|
-
const output = parser.parse(input);
|
|
123
|
-
expect(output).toBe(`h(Canvas, { width: 10 })`);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
413
|
test("should compile component with static string attribute", () => {
|
|
127
414
|
const input = `<Canvas width="val" />`;
|
|
128
415
|
const output = parser.parse(input);
|
|
129
416
|
expect(output).toBe(`h(Canvas, { width: 'val' })`);
|
|
130
417
|
});
|
|
131
418
|
|
|
419
|
+
test("should compile component with static string attribute with dash", () => {
|
|
420
|
+
const input = `<Canvas max-width="val" />`;
|
|
421
|
+
const output = parser.parse(input);
|
|
422
|
+
expect(output).toBe(`h(Canvas, { 'max-width': 'val' })`);
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
test("should compile component with static attribute (with number)", () => {
|
|
426
|
+
const input = `<Canvas width="10" />`;
|
|
427
|
+
const output = parser.parse(input);
|
|
428
|
+
expect(output).toBe(`h(Canvas, { width: '10' })`);
|
|
429
|
+
});
|
|
430
|
+
|
|
132
431
|
test("should compile component with children", () => {
|
|
133
432
|
const input = `
|
|
134
433
|
<Canvas>
|
|
@@ -142,6 +441,18 @@ describe("Compiler", () => {
|
|
|
142
441
|
);
|
|
143
442
|
});
|
|
144
443
|
|
|
444
|
+
test("should compile component with multiple children", () => {
|
|
445
|
+
const input = `<Container>
|
|
446
|
+
<Container></Container>
|
|
447
|
+
<Container></Container>
|
|
448
|
+
</Container>
|
|
449
|
+
`;
|
|
450
|
+
const output = parser.parse(input);
|
|
451
|
+
expect(output.replace(/\s+/g, "")).toBe(
|
|
452
|
+
`h(Container,null,[h(Container),h(Container)])`.replace(/\s+/g, "")
|
|
453
|
+
);
|
|
454
|
+
});
|
|
455
|
+
|
|
145
456
|
test("should compile component with multi children", () => {
|
|
146
457
|
const input = `
|
|
147
458
|
<Sprite />
|
|
@@ -154,28 +465,69 @@ describe("Compiler", () => {
|
|
|
154
465
|
});
|
|
155
466
|
|
|
156
467
|
test("should compile component with event handler", () => {
|
|
157
|
-
const input = `<Sprite
|
|
468
|
+
const input = `<Sprite click={fn} />`;
|
|
158
469
|
const output = parser.parse(input);
|
|
159
470
|
expect(output).toBe(`h(Sprite, { click: fn })`);
|
|
160
471
|
});
|
|
161
472
|
|
|
162
473
|
test("should compile component with standalone event handler", () => {
|
|
163
|
-
const input = `<Sprite
|
|
474
|
+
const input = `<Sprite click />`;
|
|
164
475
|
const output = parser.parse(input);
|
|
165
476
|
expect(output).toBe(`h(Sprite, { click })`);
|
|
166
477
|
});
|
|
167
478
|
|
|
168
479
|
test('should compile component with inline event handler', () => {
|
|
169
|
-
const input = `<Sprite
|
|
480
|
+
const input = `<Sprite click={() => console.log('click')} />`;
|
|
170
481
|
const output = parser.parse(input);
|
|
171
482
|
expect(output).toBe(`h(Sprite, { click: () => console.log('click') })`);
|
|
172
483
|
});
|
|
173
484
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
485
|
+
test("should compile component with component attribute", () => {
|
|
486
|
+
const input = `<Canvas child={<Sprite />} />`;
|
|
487
|
+
const output = parser.parse(input);
|
|
488
|
+
expect(output).toBe(`h(Canvas, { child: h(Sprite) })`);
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
test("should compile component with function returns component attribute", () => {
|
|
492
|
+
const input = `<Canvas child={() => <Sprite />} />`;
|
|
493
|
+
const output = parser.parse(input);
|
|
494
|
+
expect(output).toBe(`h(Canvas, { child: () => h(Sprite) })`);
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
test("should compile component with function (with params) returns component attribute", () => {
|
|
498
|
+
const input = `<Canvas child={(x, y) => <Sprite />} />`;
|
|
499
|
+
const output = parser.parse(input);
|
|
500
|
+
expect(output).toBe(`h(Canvas, { child: (x, y) => h(Sprite) })`);
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
test("should compile component with destructuring function (with params)", () => {
|
|
504
|
+
const input = `<Canvas child={({ x, y }) => <Sprite />} />`;
|
|
505
|
+
const output = parser.parse(input);
|
|
506
|
+
expect(output).toBe(`h(Canvas, { child: ({x, y}) => h(Sprite) })`);
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
test("should compile component with function returns component attribute and data", () => {
|
|
510
|
+
const input = `<Canvas child={() => <Text text="Hello" />} />`;
|
|
511
|
+
const output = parser.parse(input);
|
|
512
|
+
expect(output).toBe(`h(Canvas, { child: () => h(Text, { text: 'Hello' }) })`);
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
test("should compile component with function returns component attribute and child", () => {
|
|
516
|
+
const input = `<Canvas child={() => <Container>
|
|
517
|
+
<Text text="Hello" />
|
|
518
|
+
</Container>} />`;
|
|
519
|
+
const output = parser.parse(input);
|
|
520
|
+
expect(output).toBe(`h(Canvas, { child: () => h(Container, null, h(Text, { text: 'Hello' })) })`);
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
test("should compile component with function returns component attribute and children", () => {
|
|
524
|
+
const input = `<Canvas child={() => <Container>
|
|
525
|
+
<Text text="Hello 1" />
|
|
526
|
+
<Text text="Hello 2" />
|
|
527
|
+
</Container>} />`;
|
|
528
|
+
const output = parser.parse(input);
|
|
529
|
+
expect(output).toBe(`h(Canvas, { child: () => h(Container, null, [h(Text, { text: 'Hello 1' }), h(Text, { text: 'Hello 2' })]) })`);
|
|
530
|
+
});
|
|
179
531
|
});
|
|
180
532
|
|
|
181
533
|
describe("Loop", () => {
|
|
@@ -189,7 +541,7 @@ describe("Loop", () => {
|
|
|
189
541
|
`;
|
|
190
542
|
const output = parser.parse(input);
|
|
191
543
|
expect(output.replace(/\s+/g, "")).toBe(
|
|
192
|
-
`h(Canvas,null,loop(sprites,
|
|
544
|
+
`h(Canvas,null,loop(sprites,sprite=>h(Sprite)))`.replace(/\s+/g, "")
|
|
193
545
|
);
|
|
194
546
|
});
|
|
195
547
|
|
|
@@ -201,7 +553,79 @@ describe("Loop", () => {
|
|
|
201
553
|
`;
|
|
202
554
|
const output = parser.parse(input);
|
|
203
555
|
expect(output.replace(/\s+/g, "")).toBe(
|
|
204
|
-
`loop(sprites,
|
|
556
|
+
`loop(sprites,sprite=>h(Sprite))`.replace(/\s+/g, "")
|
|
557
|
+
);
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
test("should compile loop with object", () => {
|
|
561
|
+
const input = `
|
|
562
|
+
@for (sprite of sprites.items) {
|
|
563
|
+
<Sprite />
|
|
564
|
+
}
|
|
565
|
+
`;
|
|
566
|
+
const output = parser.parse(input);
|
|
567
|
+
expect(output.replace(/\s+/g, "")).toBe(
|
|
568
|
+
`loop(sprites.items,sprite=>h(Sprite))`.replace(/\s+/g, "")
|
|
569
|
+
);
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
test("should compile loop with deep object", () => {
|
|
573
|
+
const input = `
|
|
574
|
+
@for (sprite of sprites.items.items) {
|
|
575
|
+
<Sprite />
|
|
576
|
+
}
|
|
577
|
+
`;
|
|
578
|
+
const output = parser.parse(input);
|
|
579
|
+
expect(output.replace(/\s+/g, "")).toBe(
|
|
580
|
+
`loop(sprites.items.items,sprite=>h(Sprite))`.replace(/\s+/g, "")
|
|
581
|
+
);
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
test("should compile loop with function", () => {
|
|
585
|
+
const input = `
|
|
586
|
+
@for (sprite of sprites()) {
|
|
587
|
+
<Sprite />
|
|
588
|
+
}
|
|
589
|
+
`;
|
|
590
|
+
const output = parser.parse(input);
|
|
591
|
+
expect(output.replace(/\s+/g, "")).toBe(
|
|
592
|
+
`loop(sprites(),sprite=>h(Sprite))`.replace(/\s+/g, "")
|
|
593
|
+
);
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
test("should compile loop with function and params", () => {
|
|
597
|
+
const input = `
|
|
598
|
+
@for (sprite of sprites(x, y)) {
|
|
599
|
+
<Sprite />
|
|
600
|
+
}
|
|
601
|
+
`;
|
|
602
|
+
const output = parser.parse(input);
|
|
603
|
+
expect(output.replace(/\s+/g, "")).toBe(
|
|
604
|
+
`loop(sprites(x,y),sprite=>h(Sprite))`.replace(/\s+/g, "")
|
|
605
|
+
);
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
test("should compile loop with object and function and params", () => {
|
|
609
|
+
const input = `
|
|
610
|
+
@for (sprite of sprites.items(x, y)) {
|
|
611
|
+
<Sprite />
|
|
612
|
+
}
|
|
613
|
+
`;
|
|
614
|
+
const output = parser.parse(input);
|
|
615
|
+
expect(output.replace(/\s+/g, "")).toBe(
|
|
616
|
+
`loop(sprites.items(x,y),sprite=>h(Sprite))`.replace(/\s+/g, "")
|
|
617
|
+
);
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
test("should compile loop with destructuring", () => {
|
|
621
|
+
const input = `
|
|
622
|
+
@for ((sprite, index) of sprites) {
|
|
623
|
+
<Sprite key={index} />
|
|
624
|
+
}
|
|
625
|
+
`;
|
|
626
|
+
const output = parser.parse(input);
|
|
627
|
+
expect(output.replace(/\s+/g, "")).toBe(
|
|
628
|
+
`loop(sprites,(sprite,index)=>h(Sprite, { key: index }))`.replace(/\s+/g, "")
|
|
205
629
|
);
|
|
206
630
|
});
|
|
207
631
|
|
|
@@ -215,7 +639,7 @@ describe("Loop", () => {
|
|
|
215
639
|
`;
|
|
216
640
|
const output = parser.parse(input);
|
|
217
641
|
expect(output.replace(/\s+/g, "")).toBe(
|
|
218
|
-
`loop(sprites,
|
|
642
|
+
`loop(sprites,sprite=>loop(others,other=>h(Sprite)))`.replace(
|
|
219
643
|
/\s+/g,
|
|
220
644
|
""
|
|
221
645
|
)
|
|
@@ -224,6 +648,46 @@ describe("Loop", () => {
|
|
|
224
648
|
});
|
|
225
649
|
|
|
226
650
|
describe("Condition", () => {
|
|
651
|
+
test("should compile condition", () => {
|
|
652
|
+
const input = `
|
|
653
|
+
@if (sprite) {
|
|
654
|
+
<Sprite />
|
|
655
|
+
}
|
|
656
|
+
`;
|
|
657
|
+
const output = parser.parse(input);
|
|
658
|
+
expect(output).toBe(`cond(sprite, () => h(Sprite))`);
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
test("should compile negative condition", () => {
|
|
662
|
+
const input = `
|
|
663
|
+
@if (!sprite) {
|
|
664
|
+
<Sprite />
|
|
665
|
+
}
|
|
666
|
+
`;
|
|
667
|
+
const output = parser.parse(input);
|
|
668
|
+
expect(output).toBe(`cond(computed(() => !sprite()), () => h(Sprite))`);
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
test("should compile negative condition with multiple condition", () => {
|
|
672
|
+
const input = `
|
|
673
|
+
@if (!sprite && other) {
|
|
674
|
+
<Sprite />
|
|
675
|
+
}
|
|
676
|
+
`;
|
|
677
|
+
const output = parser.parse(input);
|
|
678
|
+
expect(output).toBe(`cond(computed(() => !sprite() && other()), () => h(Sprite))`);
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
test("should compile negative condition with multiple condition (or)", () => {
|
|
682
|
+
const input = `
|
|
683
|
+
@if (!sprite || other) {
|
|
684
|
+
<Sprite />
|
|
685
|
+
}
|
|
686
|
+
`;
|
|
687
|
+
const output = parser.parse(input);
|
|
688
|
+
expect(output).toBe(`cond(computed(() => !sprite() || other()), () => h(Sprite))`);
|
|
689
|
+
});
|
|
690
|
+
|
|
227
691
|
test("should compile condition when sprite is visible", () => {
|
|
228
692
|
const input = `
|
|
229
693
|
@if (sprite.visible) {
|
|
@@ -234,6 +698,16 @@ describe("Condition", () => {
|
|
|
234
698
|
expect(output).toBe(`cond(sprite.visible, () => h(Sprite))`);
|
|
235
699
|
});
|
|
236
700
|
|
|
701
|
+
test("should compile condition when function value", () => {
|
|
702
|
+
const input = `
|
|
703
|
+
@if (val()) {
|
|
704
|
+
<Sprite />
|
|
705
|
+
}
|
|
706
|
+
`;
|
|
707
|
+
const output = parser.parse(input);
|
|
708
|
+
expect(output).toBe(`cond(val(), () => h(Sprite))`);
|
|
709
|
+
});
|
|
710
|
+
|
|
237
711
|
test("should compile condition for multiple sprites", () => {
|
|
238
712
|
const input = `
|
|
239
713
|
@if (sprite) {
|
|
@@ -293,6 +767,43 @@ describe("Condition", () => {
|
|
|
293
767
|
`cond(sprite.visible, () => [h(Sprite), cond(deep, () => h(Sprite)), h(Sprite)])`
|
|
294
768
|
);
|
|
295
769
|
});
|
|
770
|
+
|
|
771
|
+
test("should compile if/else within canvas", () => {
|
|
772
|
+
const input = `
|
|
773
|
+
<Canvas>
|
|
774
|
+
@if (showSprite) {
|
|
775
|
+
<Sprite />
|
|
776
|
+
}
|
|
777
|
+
@else {
|
|
778
|
+
<Text text="No sprite" />
|
|
779
|
+
}
|
|
780
|
+
</Canvas>
|
|
781
|
+
`;
|
|
782
|
+
const output = parser.parse(input);
|
|
783
|
+
expect(output).toBe(
|
|
784
|
+
`h(Canvas, null, cond(showSprite, () => h(Sprite), () => h(Text, { text: 'No sprite' })))`
|
|
785
|
+
);
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
test("should compile if/else if/else within loop", () => {
|
|
789
|
+
const input = `
|
|
790
|
+
@for (item of items) {
|
|
791
|
+
@if (item.type === 'sprite') {
|
|
792
|
+
<Sprite />
|
|
793
|
+
}
|
|
794
|
+
@else if (item.type === 'text') {
|
|
795
|
+
<Text />
|
|
796
|
+
}
|
|
797
|
+
@else {
|
|
798
|
+
<Container />
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
`;
|
|
802
|
+
const output = parser.parse(input);
|
|
803
|
+
expect(output.replace(/\s+/g, "")).toBe(
|
|
804
|
+
`loop(items,item=>cond(computed(()=>item().type()==='sprite'),()=>h(Sprite),[computed(()=>item().type()==='text'),()=>h(Text)],()=>h(Container)))`.replace(/\s+/g, "")
|
|
805
|
+
);
|
|
806
|
+
});
|
|
296
807
|
});
|
|
297
808
|
|
|
298
809
|
describe("Condition in Loops", () => {
|
|
@@ -308,7 +819,7 @@ describe("Condition in Loops", () => {
|
|
|
308
819
|
`;
|
|
309
820
|
const output = parser.parse(input);
|
|
310
821
|
expect(output.replace(/\s+/g, "")).toBe(
|
|
311
|
-
`h(Canvas,null,loop(sprites,
|
|
822
|
+
`h(Canvas,null,loop(sprites,sprite=>cond(sprite.visible,()=>h(Sprite))))`.replace(/\s+/g, "")
|
|
312
823
|
);
|
|
313
824
|
});
|
|
314
825
|
|
|
@@ -322,7 +833,7 @@ describe("Condition in Loops", () => {
|
|
|
322
833
|
`;
|
|
323
834
|
const output = parser.parse(input);
|
|
324
835
|
expect(output.replace(/\s+/g, "")).toBe(
|
|
325
|
-
`h(Canvas,null,loop(sprites,
|
|
836
|
+
`h(Canvas,null,loop(sprites,sprite=>h(Sprite)))`.replace(/\s+/g, "")
|
|
326
837
|
);
|
|
327
838
|
});
|
|
328
839
|
|
|
@@ -339,8 +850,310 @@ describe("Condition in Loops", () => {
|
|
|
339
850
|
`;
|
|
340
851
|
const output = parser.parse(input);
|
|
341
852
|
expect(output.replace(/\s+/g, "")).toBe(
|
|
342
|
-
`h(Canvas,null,[loop(sprites,
|
|
853
|
+
`h(Canvas,null,[loop(sprites,sprite=>h(Sprite)),loop(others,other=>h(Sprite))])`.replace(/\s+/g, "")
|
|
343
854
|
);
|
|
344
855
|
});
|
|
345
856
|
|
|
346
857
|
});
|
|
858
|
+
|
|
859
|
+
describe('Svg', () => {
|
|
860
|
+
test('should compile svg', () => {
|
|
861
|
+
const input = `<svg>
|
|
862
|
+
<path d="M 100 350 l 150 -300" stroke="red" stroke-width="4"/>
|
|
863
|
+
</svg>`;
|
|
864
|
+
const output = parser.parse(input);
|
|
865
|
+
expect(output).toBe('h(Svg, { content: `<svg><path d="M 100 350 l 150 -300" stroke="red" stroke-width="4"/></svg>` })');
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
test('should compile svg with canvas', () => {
|
|
869
|
+
const input = `<Canvas antialias={true}>
|
|
870
|
+
<svg height="400" width="450" xmlns="http://www.w3.org/2000/svg"></svg></Canvas>`;
|
|
871
|
+
const output = parser.parse(input);
|
|
872
|
+
expect(output).toBe('h(Canvas, { antialias: true }, h(Svg, { content: `<svg height="400" width="450" xmlns="http://www.w3.org/2000/svg"></svg>` }))');
|
|
873
|
+
});
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
describe('DOM', () => {
|
|
877
|
+
test('should compile input DOM', () => {
|
|
878
|
+
const input = `<input type="text" />`;
|
|
879
|
+
const output = parser.parse(input);
|
|
880
|
+
expect(output).toBe('h(DOMElement, { element: "input", attrs: { type: \'text\' } })');
|
|
881
|
+
});
|
|
882
|
+
|
|
883
|
+
test('should compile div DOM', () => {
|
|
884
|
+
const input = `<div class="container" />`;
|
|
885
|
+
const output = parser.parse(input);
|
|
886
|
+
expect(output).toBe('h(DOMElement, { element: "div", attrs: { class: \'container\' } })');
|
|
887
|
+
});
|
|
888
|
+
|
|
889
|
+
test('should compile button DOM', () => {
|
|
890
|
+
const input = `<button type="submit" />`;
|
|
891
|
+
const output = parser.parse(input);
|
|
892
|
+
expect(output).toBe('h(DOMElement, { element: "button", attrs: { type: \'submit\' } })');
|
|
893
|
+
});
|
|
894
|
+
|
|
895
|
+
test('should compile button DOM', () => {
|
|
896
|
+
const input = `<button>Text</button>`;
|
|
897
|
+
const output = parser.parse(input);
|
|
898
|
+
expect(output).toBe('h(DOMElement, { element: "button", textContent: \'Text\' })');
|
|
899
|
+
});
|
|
900
|
+
|
|
901
|
+
test('should compile textarea DOM with dynamic attributes', () => {
|
|
902
|
+
const input = `<textarea rows={rows} cols={cols} />`;
|
|
903
|
+
const output = parser.parse(input);
|
|
904
|
+
expect(output).toBe('h(DOMElement, { element: "textarea", attrs: { rows: rows, cols: cols } })');
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
test('should not transform Canvas to DOM', () => {
|
|
908
|
+
const input = `<Canvas width={800} />`;
|
|
909
|
+
const output = parser.parse(input);
|
|
910
|
+
expect(output).toBe('h(Canvas, { width: 800 })');
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
test('should not transform Sprite to DOM', () => {
|
|
914
|
+
const input = `<Sprite image="test.png" />`;
|
|
915
|
+
const output = parser.parse(input);
|
|
916
|
+
expect(output).toBe('h(Sprite, { image: \'test.png\' })');
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
// Tests avec imbrications DOM
|
|
920
|
+
test('should compile nested DOM elements', () => {
|
|
921
|
+
const input = `<div class="container">
|
|
922
|
+
<p>Hello World</p>
|
|
923
|
+
</div>`;
|
|
924
|
+
const output = parser.parse(input);
|
|
925
|
+
expect(output).toBe('h(DOMElement, { element: "div", attrs: { class: \'container\' } }, h(DOMElement, { element: "p", textContent: \'Hello World\' }))');
|
|
926
|
+
});
|
|
927
|
+
|
|
928
|
+
test('should compile deeply nested DOM elements', () => {
|
|
929
|
+
const input = `<div class="wrapper">
|
|
930
|
+
<section>
|
|
931
|
+
<h1>Title</h1>
|
|
932
|
+
<p>Content</p>
|
|
933
|
+
</section>
|
|
934
|
+
</div>`;
|
|
935
|
+
const output = parser.parse(input);
|
|
936
|
+
expect(output.replace(/\s+/g, "")).toBe(
|
|
937
|
+
`h(DOMElement, { element: "div", attrs: { class: 'wrapper' } }, h(DOMElement, { element: "section" }, [h(DOMElement, { element: "h1", textContent: 'Title' }), h(DOMElement, { element: "p", textContent: 'Content' })]))`.replace(/\s+/g, "")
|
|
938
|
+
);
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
test('should compile DOM with multiple children', () => {
|
|
942
|
+
const input = `<ul>
|
|
943
|
+
<li>Item 1</li>
|
|
944
|
+
<li>Item 2</li>
|
|
945
|
+
<li>Item 3</li>
|
|
946
|
+
</ul>`;
|
|
947
|
+
const output = parser.parse(input);
|
|
948
|
+
expect(output.replace(/\s+/g, "")).toBe(
|
|
949
|
+
`h(DOMElement, { element: "ul" }, [h(DOMElement, { element: "li", textContent: 'Item 1' }), h(DOMElement, { element: "li", textContent: 'Item 2' }), h(DOMElement, { element: "li", textContent: 'Item 3' })])`.replace(/\s+/g, "")
|
|
950
|
+
);
|
|
951
|
+
});
|
|
952
|
+
|
|
953
|
+
test('should compile mixed DOM and framework components', () => {
|
|
954
|
+
const input = `<div class="game-container">
|
|
955
|
+
<Canvas width={800} height={600} />
|
|
956
|
+
<div class="ui">
|
|
957
|
+
<button>Start Game</button>
|
|
958
|
+
</div>
|
|
959
|
+
</div>`;
|
|
960
|
+
const output = parser.parse(input);
|
|
961
|
+
expect(output.replace(/\s+/g, "")).toBe(
|
|
962
|
+
`h(DOMElement, { element: "div", attrs: { class: 'game-container' } }, [h(Canvas, { width: 800, height: 600 }), h(DOMElement, { element: "div", attrs: { class: 'ui' } }, h(DOMElement, { element: "button", textContent: 'Start Game' }))])`.replace(/\s+/g, "")
|
|
963
|
+
);
|
|
964
|
+
});
|
|
965
|
+
|
|
966
|
+
test('should compile DOM with attributes and text content', () => {
|
|
967
|
+
const input = `<button class="btn primary" type="submit">Submit Form</button>`;
|
|
968
|
+
const output = parser.parse(input);
|
|
969
|
+
expect(output).toBe('h(DOMElement, { element: "button", attrs: { class: \'btn primary\', type: \'submit\' }, textContent: \'Submit Form\' })');
|
|
970
|
+
});
|
|
971
|
+
});
|
|
972
|
+
|
|
973
|
+
describe('DOM with special attributes', () => {
|
|
974
|
+
test('should compile DOM with special attributes', () => {
|
|
975
|
+
const input = `<input type="password" x={100} y={100} />`;
|
|
976
|
+
const output = parser.parse(input);
|
|
977
|
+
expect(output).toBe('h(DOMElement, { element: "input", attrs: { type: \'password\' }, x: 100, y: 100 })');
|
|
978
|
+
});
|
|
979
|
+
|
|
980
|
+
test('should compile DOM with text object', () => {
|
|
981
|
+
const input = `<p>{{ object.x }}</p>`;
|
|
982
|
+
const output = parser.parse(input);
|
|
983
|
+
expect(output).toBe('h(DOMElement, { element: "p", textContent: computed(() => object().x()) })');
|
|
984
|
+
});
|
|
985
|
+
|
|
986
|
+
test('should compile DOM with text object with @', () => {
|
|
987
|
+
const input = `<p>{{ @object.x }}</p>`;
|
|
988
|
+
const output = parser.parse(input);
|
|
989
|
+
expect(output).toBe('h(DOMElement, { element: "p", textContent: computed(() => object.x()) })');
|
|
990
|
+
});
|
|
991
|
+
|
|
992
|
+
test('should compile DOM with text object with literal ', () => {
|
|
993
|
+
const input = `<p>{{ @object.@x }}</p>`;
|
|
994
|
+
const output = parser.parse(input);
|
|
995
|
+
expect(output).toBe('h(DOMElement, { element: "p", textContent: object.x })');
|
|
996
|
+
});
|
|
997
|
+
});
|
|
998
|
+
|
|
999
|
+
describe('DOM with Control Structures', () => {
|
|
1000
|
+
test('should compile @for loop with DOM elements', () => {
|
|
1001
|
+
const input = `
|
|
1002
|
+
@for (item of items) {
|
|
1003
|
+
<li>{item.name}</li>
|
|
1004
|
+
}
|
|
1005
|
+
`;
|
|
1006
|
+
const output = parser.parse(input);
|
|
1007
|
+
expect(output.replace(/\s+/g, "")).toBe(
|
|
1008
|
+
`loop(items, item => h(DOMElement, { element: "li", textContent: computed(() => item().name()) }))`.replace(/\s+/g, "")
|
|
1009
|
+
);
|
|
1010
|
+
});
|
|
1011
|
+
|
|
1012
|
+
test('Use literal text content', () => {
|
|
1013
|
+
const input = `
|
|
1014
|
+
<p>{@text}</p>
|
|
1015
|
+
`;
|
|
1016
|
+
const output = parser.parse(input);
|
|
1017
|
+
expect(output.replace(/\s+/g, "")).toBe(
|
|
1018
|
+
`h(DOMElement, { element: "p", textContent: text })`.replace(/\s+/g, "")
|
|
1019
|
+
);
|
|
1020
|
+
})
|
|
1021
|
+
|
|
1022
|
+
test('should compile @for loop with nested DOM structure', () => {
|
|
1023
|
+
const input = `
|
|
1024
|
+
<ul class="menu">
|
|
1025
|
+
@for (item of menuItems) {
|
|
1026
|
+
<li class="menu-item">
|
|
1027
|
+
<a href={item.url}>{item.title}</a>
|
|
1028
|
+
</li>
|
|
1029
|
+
}
|
|
1030
|
+
</ul>
|
|
1031
|
+
`;
|
|
1032
|
+
const output = parser.parse(input);
|
|
1033
|
+
expect(output.replace(/\s+/g, "")).toBe(
|
|
1034
|
+
`h(DOMElement, { element: "ul", attrs: { class: 'menu' } }, loop(menuItems, item => h(DOMElement, { element: "li", attrs: { class: 'menu-item' } }, h(DOMElement, { element: "a", attrs: { href: computed(() => item().url()) }, textContent: computed(() => item().title()) }))))`.replace(/\s+/g, "")
|
|
1035
|
+
);
|
|
1036
|
+
});
|
|
1037
|
+
|
|
1038
|
+
test('should compile @if condition with DOM elements', () => {
|
|
1039
|
+
const input = `
|
|
1040
|
+
@if (showMessage) {
|
|
1041
|
+
<div class="alert">
|
|
1042
|
+
<p>Important message!</p>
|
|
1043
|
+
</div>
|
|
1044
|
+
}
|
|
1045
|
+
`;
|
|
1046
|
+
const output = parser.parse(input);
|
|
1047
|
+
expect(output.replace(/\s+/g, "")).toBe(
|
|
1048
|
+
`cond(showMessage, () => h(DOMElement, { element: "div", attrs: { class: 'alert' } }, h(DOMElement, { element: "p", textContent: 'Important message!' })))`.replace(/\s+/g, "")
|
|
1049
|
+
);
|
|
1050
|
+
});
|
|
1051
|
+
|
|
1052
|
+
test('should compile @if condition with simple DOM element', () => {
|
|
1053
|
+
const input = `
|
|
1054
|
+
@if (isVisible) {
|
|
1055
|
+
<button>Click me</button>
|
|
1056
|
+
}
|
|
1057
|
+
`;
|
|
1058
|
+
const output = parser.parse(input);
|
|
1059
|
+
expect(output.replace(/\s+/g, "")).toBe(
|
|
1060
|
+
`cond(isVisible, () => h(DOMElement, { element: "button", textContent: 'Click me' }))`.replace(/\s+/g, "")
|
|
1061
|
+
);
|
|
1062
|
+
});
|
|
1063
|
+
|
|
1064
|
+
test('should compile nested @for and @if with DOM', () => {
|
|
1065
|
+
const input = `
|
|
1066
|
+
<div class="container">
|
|
1067
|
+
@for (section of sections) {
|
|
1068
|
+
@if (section.visible) {
|
|
1069
|
+
<section class="content">
|
|
1070
|
+
<h2>{section.title}</h2>
|
|
1071
|
+
<div class="items">
|
|
1072
|
+
@for (item of section.items) {
|
|
1073
|
+
<div class="item">{item.name}</div>
|
|
1074
|
+
}
|
|
1075
|
+
</div>
|
|
1076
|
+
</section>
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
</div>
|
|
1080
|
+
`;
|
|
1081
|
+
const output = parser.parse(input);
|
|
1082
|
+
expect(output.replace(/\s+/g, "")).toBe(
|
|
1083
|
+
`h(DOMElement, { element: "div", attrs: { class: 'container' } }, loop(sections, section => cond(section.visible, () => h(DOMElement, { element: "section", attrs: { class: 'content' } }, [h(DOMElement, { element: "h2", textContent: computed(() => section().title()) }), h(DOMElement, { element: "div", attrs: { class: 'items' } }, loop(section.items, item => h(DOMElement, { element: "div", attrs: { class: 'item' }, textContent: computed(() => item().name()) })))]))))`.replace(/\s+/g, "")
|
|
1084
|
+
);
|
|
1085
|
+
});
|
|
1086
|
+
|
|
1087
|
+
test('should compile @for with DOM table structure', () => {
|
|
1088
|
+
const input = `
|
|
1089
|
+
<table>
|
|
1090
|
+
<thead>
|
|
1091
|
+
<tr>
|
|
1092
|
+
<th>Name</th>
|
|
1093
|
+
<th>Age</th>
|
|
1094
|
+
</tr>
|
|
1095
|
+
</thead>
|
|
1096
|
+
<tbody>
|
|
1097
|
+
@for (user of users) {
|
|
1098
|
+
<tr>
|
|
1099
|
+
<td>{user.name}</td>
|
|
1100
|
+
<td>{user.age}</td>
|
|
1101
|
+
</tr>
|
|
1102
|
+
}
|
|
1103
|
+
</tbody>
|
|
1104
|
+
</table>
|
|
1105
|
+
`;
|
|
1106
|
+
const output = parser.parse(input);
|
|
1107
|
+
expect(output.replace(/\s+/g, "")).toBe(
|
|
1108
|
+
`h(DOMElement, { element: "table" }, [h(DOMElement, { element: "thead" }, h(DOMElement, { element: "tr" }, [h(DOMElement, { element: "th", textContent: 'Name' }), h(DOMElement, { element: "th", textContent: 'Age' })])), h(DOMElement, { element: "tbody" }, loop(users, user => h(DOMElement, { element: "tr" }, [h(DOMElement, { element: "td", textContent: computed(() => user().name()) }), h(DOMElement, { element: "td", textContent: computed(() => user().age()) })])))])`.replace(/\s+/g, "")
|
|
1109
|
+
);
|
|
1110
|
+
});
|
|
1111
|
+
|
|
1112
|
+
test('should compile @if with multiple DOM conditions', () => {
|
|
1113
|
+
const input = `
|
|
1114
|
+
<div class="status">
|
|
1115
|
+
@if (isLoading) {
|
|
1116
|
+
<div class="spinner">Loading...</div>
|
|
1117
|
+
}
|
|
1118
|
+
@if (hasError) {
|
|
1119
|
+
<div class="error">Error occurred!</div>
|
|
1120
|
+
}
|
|
1121
|
+
@if (isSuccess) {
|
|
1122
|
+
<div class="success">Success!</div>
|
|
1123
|
+
}
|
|
1124
|
+
</div>
|
|
1125
|
+
`;
|
|
1126
|
+
const output = parser.parse(input);
|
|
1127
|
+
expect(output.replace(/\s+/g, "")).toBe(
|
|
1128
|
+
`h(DOMElement, { element: "div", attrs: { class: 'status' } }, [cond(isLoading, () => h(DOMElement, { element: "div", attrs: { class: 'spinner' }, textContent: 'Loading...' })), cond(hasError, () => h(DOMElement, { element: "div", attrs: { class: 'error' }, textContent: 'Error occurred!' })), cond(isSuccess, () => h(DOMElement, { element: "div", attrs: { class: 'success' }, textContent: 'Success!' }))])`.replace(/\s+/g, "")
|
|
1129
|
+
);
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1132
|
+
test('should compile mixed Canvas and DOM with control structures', () => {
|
|
1133
|
+
const input = `
|
|
1134
|
+
<div class="game-wrapper">
|
|
1135
|
+
<Canvas width={800} height={600}>
|
|
1136
|
+
@for (sprite of sprites) {
|
|
1137
|
+
<Sprite x={sprite.x} y={sprite.y} />
|
|
1138
|
+
}
|
|
1139
|
+
</Canvas>
|
|
1140
|
+
<div class="ui-overlay">
|
|
1141
|
+
@if (showScore) {
|
|
1142
|
+
<div class="score">Score: {score}</div>
|
|
1143
|
+
}
|
|
1144
|
+
@if (showMenu) {
|
|
1145
|
+
<div class="menu">
|
|
1146
|
+
@for (option of menuOptions) {
|
|
1147
|
+
<button class="menu-btn">{option.label}</button>
|
|
1148
|
+
}
|
|
1149
|
+
</div>
|
|
1150
|
+
}
|
|
1151
|
+
</div>
|
|
1152
|
+
</div>
|
|
1153
|
+
`;
|
|
1154
|
+
const output = parser.parse(input);
|
|
1155
|
+
expect(output.replace(/\s+/g, "")).toBe(
|
|
1156
|
+
`h(DOMElement, { element: "div", attrs: { class: 'game-wrapper' } }, [h(Canvas, { width: 800, height: 600 }, loop(sprites, sprite => h(Sprite, { x: computed(() => sprite().x()), y: computed(() => sprite().y()) }))), h(DOMElement, { element: "div", attrs: { class: 'ui-overlay' } }, [cond(showScore, () => h(DOMElement, { element: "div", attrs: { class: 'score' }, textContent: computed(() => 'Score: ' + computed(() => score())) })), cond(showMenu, () => h(DOMElement, { element: "div", attrs: { class: 'menu' } }, loop(menuOptions, option => h(DOMElement, { element: "button", attrs: { class: 'menu-btn' }, textContent: computed(() => option().label()) }))))])])`.replace(/\s+/g, "")
|
|
1157
|
+
);
|
|
1158
|
+
});
|
|
1159
|
+
});
|