@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.
@@ -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: computed(() => ({x: 10, y: 20})) })`);
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: computed(() => deep.value) })`);
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: computed(() => ({x: x(), y: 20})) })`);
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: computed(() => [10, 20]) })`);
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: computed(() => [x(), 20]) })`);
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 @click={fn} />`;
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 @click />`;
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 @click={() => console.log('click')} />`;
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
- // test("should compile component with component attribute", () => {
175
- // const input = `<Canvas child={<Sprite />} />`;
176
- // const output = parser.parse(input);
177
- // expect(output).toBe(`h(Canvas, { child: h(Sprite) })`);
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,(sprite)=>h(Sprite)))`.replace(/\s+/g, "")
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,(sprite)=>h(Sprite))`.replace(/\s+/g, "")
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,(sprite)=>loop(others,(other)=>h(Sprite)))`.replace(
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,(sprite)=>cond(sprite.visible,()=>h(Sprite))))`.replace(/\s+/g, "")
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,(sprite)=>h(Sprite)))`.replace(/\s+/g, "")
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,(sprite)=>h(Sprite)),loop(others,(other)=>h(Sprite))])`.replace(/\s+/g, "")
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
+ });