@canvasengine/compiler 2.0.0-beta.2 → 2.0.0-beta.20

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,106 @@ 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
+
60
135
  test("should compile component with object attribute", () => {
61
136
  const input = `<Canvas width={ {x: 10, y: 20} } />`;
62
137
  const output = parser.parse(input);
63
- expect(output).toBe(`h(Canvas, { width: computed(() => ({x: 10, y: 20})) })`);
138
+ expect(output).toBe(`h(Canvas, { width: ({x: 10, y: 20}) })`);
139
+ });
140
+
141
+ test("should compile component with complex object attribute", () => {
142
+ const input = `<Sprite
143
+ sheet={{
144
+ definition,
145
+ playing: "stand",
146
+ params: {
147
+ direction: "right"
148
+ },
149
+ onFinish
150
+ }}
151
+ />`;
152
+ const output = parser.parse(input);
153
+ expect(output).toBe(`h(Sprite, { sheet: { definition, playing: "stand", params: { direction: "right" }, onFinish } })`);
64
154
  });
65
155
 
66
156
  test("should compile component with deep object attribute", () => {
@@ -78,7 +168,7 @@ describe("Compiler", () => {
78
168
  test("should compile component with deep object attribute but not all transform to signal", () => {
79
169
  const input = `<Canvas width={@deep.@value} />`;
80
170
  const output = parser.parse(input);
81
- expect(output).toBe(`h(Canvas, { width: computed(() => deep.value) })`);
171
+ expect(output).toBe(`h(Canvas, { width: deep.value })`);
82
172
  });
83
173
 
84
174
  test("should compile component with dynamic object attribute", () => {
@@ -90,7 +180,7 @@ describe("Compiler", () => {
90
180
  test("should compile component with array attribute", () => {
91
181
  const input = `<Canvas width={ [10, 20] } />`;
92
182
  const output = parser.parse(input);
93
- expect(output).toBe(`h(Canvas, { width: computed(() => [10, 20]) })`);
183
+ expect(output).toBe(`h(Canvas, { width: [10, 20] })`);
94
184
  });
95
185
 
96
186
  test("should compile component with dynamic array attribute", () => {
@@ -117,18 +207,24 @@ describe("Compiler", () => {
117
207
  expect(output).toBe(`h(Canvas, { width: computed(() => x() * 2 * y()) })`);
118
208
  });
119
209
 
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
210
  test("should compile component with static string attribute", () => {
127
211
  const input = `<Canvas width="val" />`;
128
212
  const output = parser.parse(input);
129
213
  expect(output).toBe(`h(Canvas, { width: 'val' })`);
130
214
  });
131
215
 
216
+ test("should compile component with static string attribute with dash", () => {
217
+ const input = `<Canvas max-width="val" />`;
218
+ const output = parser.parse(input);
219
+ expect(output).toBe(`h(Canvas, { 'max-width': 'val' })`);
220
+ });
221
+
222
+ test("should compile component with static attribute (with number)", () => {
223
+ const input = `<Canvas width="10" />`;
224
+ const output = parser.parse(input);
225
+ expect(output).toBe(`h(Canvas, { width: '10' })`);
226
+ });
227
+
132
228
  test("should compile component with children", () => {
133
229
  const input = `
134
230
  <Canvas>
@@ -142,6 +238,18 @@ describe("Compiler", () => {
142
238
  );
143
239
  });
144
240
 
241
+ test("should compile component with multiple children", () => {
242
+ const input = `<Container>
243
+ <Container></Container>
244
+ <Container></Container>
245
+ </Container>
246
+ `;
247
+ const output = parser.parse(input);
248
+ expect(output.replace(/\s+/g, "")).toBe(
249
+ `h(Container,null,[h(Container),h(Container)])`.replace(/\s+/g, "")
250
+ );
251
+ });
252
+
145
253
  test("should compile component with multi children", () => {
146
254
  const input = `
147
255
  <Sprite />
@@ -171,11 +279,52 @@ describe("Compiler", () => {
171
279
  expect(output).toBe(`h(Sprite, { click: () => console.log('click') })`);
172
280
  });
173
281
 
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
- // });
282
+ test("should compile component with component attribute", () => {
283
+ const input = `<Canvas child={<Sprite />} />`;
284
+ const output = parser.parse(input);
285
+ expect(output).toBe(`h(Canvas, { child: h(Sprite) })`);
286
+ });
287
+
288
+ test("should compile component with function returns component attribute", () => {
289
+ const input = `<Canvas child={() => <Sprite />} />`;
290
+ const output = parser.parse(input);
291
+ expect(output).toBe(`h(Canvas, { child: () => h(Sprite) })`);
292
+ });
293
+
294
+ test("should compile component with function (with params) returns component attribute", () => {
295
+ const input = `<Canvas child={(x, y) => <Sprite />} />`;
296
+ const output = parser.parse(input);
297
+ expect(output).toBe(`h(Canvas, { child: (x, y) => h(Sprite) })`);
298
+ });
299
+
300
+ test("should compile component with destructuring function (with params)", () => {
301
+ const input = `<Canvas child={({ x, y }) => <Sprite />} />`;
302
+ const output = parser.parse(input);
303
+ expect(output).toBe(`h(Canvas, { child: ({x, y}) => h(Sprite) })`);
304
+ });
305
+
306
+ test("should compile component with function returns component attribute and data", () => {
307
+ const input = `<Canvas child={() => <Text text="Hello" />} />`;
308
+ const output = parser.parse(input);
309
+ expect(output).toBe(`h(Canvas, { child: () => h(Text, { text: 'Hello' }) })`);
310
+ });
311
+
312
+ test("should compile component with function returns component attribute and child", () => {
313
+ const input = `<Canvas child={() => <Container>
314
+ <Text text="Hello" />
315
+ </Container>} />`;
316
+ const output = parser.parse(input);
317
+ expect(output).toBe(`h(Canvas, { child: () => h(Container, null, h(Text, { text: 'Hello' })) })`);
318
+ });
319
+
320
+ test("should compile component with function returns component attribute and children", () => {
321
+ const input = `<Canvas child={() => <Container>
322
+ <Text text="Hello 1" />
323
+ <Text text="Hello 2" />
324
+ </Container>} />`;
325
+ const output = parser.parse(input);
326
+ expect(output).toBe(`h(Canvas, { child: () => h(Container, null, [h(Text, { text: 'Hello 1' }), h(Text, { text: 'Hello 2' })]) })`);
327
+ });
179
328
  });
180
329
 
181
330
  describe("Loop", () => {
@@ -189,7 +338,7 @@ describe("Loop", () => {
189
338
  `;
190
339
  const output = parser.parse(input);
191
340
  expect(output.replace(/\s+/g, "")).toBe(
192
- `h(Canvas,null,loop(sprites,(sprite)=>h(Sprite)))`.replace(/\s+/g, "")
341
+ `h(Canvas,null,loop(sprites,sprite=>h(Sprite)))`.replace(/\s+/g, "")
193
342
  );
194
343
  });
195
344
 
@@ -201,7 +350,79 @@ describe("Loop", () => {
201
350
  `;
202
351
  const output = parser.parse(input);
203
352
  expect(output.replace(/\s+/g, "")).toBe(
204
- `loop(sprites,(sprite)=>h(Sprite))`.replace(/\s+/g, "")
353
+ `loop(sprites,sprite=>h(Sprite))`.replace(/\s+/g, "")
354
+ );
355
+ });
356
+
357
+ test("should compile loop with object", () => {
358
+ const input = `
359
+ @for (sprite of sprites.items) {
360
+ <Sprite />
361
+ }
362
+ `;
363
+ const output = parser.parse(input);
364
+ expect(output.replace(/\s+/g, "")).toBe(
365
+ `loop(sprites.items,sprite=>h(Sprite))`.replace(/\s+/g, "")
366
+ );
367
+ });
368
+
369
+ test("should compile loop with deep object", () => {
370
+ const input = `
371
+ @for (sprite of sprites.items.items) {
372
+ <Sprite />
373
+ }
374
+ `;
375
+ const output = parser.parse(input);
376
+ expect(output.replace(/\s+/g, "")).toBe(
377
+ `loop(sprites.items.items,sprite=>h(Sprite))`.replace(/\s+/g, "")
378
+ );
379
+ });
380
+
381
+ test("should compile loop with function", () => {
382
+ const input = `
383
+ @for (sprite of sprites()) {
384
+ <Sprite />
385
+ }
386
+ `;
387
+ const output = parser.parse(input);
388
+ expect(output.replace(/\s+/g, "")).toBe(
389
+ `loop(sprites(),sprite=>h(Sprite))`.replace(/\s+/g, "")
390
+ );
391
+ });
392
+
393
+ test("should compile loop with function and params", () => {
394
+ const input = `
395
+ @for (sprite of sprites(x, y)) {
396
+ <Sprite />
397
+ }
398
+ `;
399
+ const output = parser.parse(input);
400
+ expect(output.replace(/\s+/g, "")).toBe(
401
+ `loop(sprites(x,y),sprite=>h(Sprite))`.replace(/\s+/g, "")
402
+ );
403
+ });
404
+
405
+ test("should compile loop with object and function and params", () => {
406
+ const input = `
407
+ @for (sprite of sprites.items(x, y)) {
408
+ <Sprite />
409
+ }
410
+ `;
411
+ const output = parser.parse(input);
412
+ expect(output.replace(/\s+/g, "")).toBe(
413
+ `loop(sprites.items(x,y),sprite=>h(Sprite))`.replace(/\s+/g, "")
414
+ );
415
+ });
416
+
417
+ test("should compile loop with destructuring", () => {
418
+ const input = `
419
+ @for ((sprite, index) of sprites) {
420
+ <Sprite key={index} />
421
+ }
422
+ `;
423
+ const output = parser.parse(input);
424
+ expect(output.replace(/\s+/g, "")).toBe(
425
+ `loop(sprites,(sprite,index)=>h(Sprite, { key: index }))`.replace(/\s+/g, "")
205
426
  );
206
427
  });
207
428
 
@@ -215,7 +436,7 @@ describe("Loop", () => {
215
436
  `;
216
437
  const output = parser.parse(input);
217
438
  expect(output.replace(/\s+/g, "")).toBe(
218
- `loop(sprites,(sprite)=>loop(others,(other)=>h(Sprite)))`.replace(
439
+ `loop(sprites,sprite=>loop(others,other=>h(Sprite)))`.replace(
219
440
  /\s+/g,
220
441
  ""
221
442
  )
@@ -224,6 +445,46 @@ describe("Loop", () => {
224
445
  });
225
446
 
226
447
  describe("Condition", () => {
448
+ test("should compile condition", () => {
449
+ const input = `
450
+ @if (sprite) {
451
+ <Sprite />
452
+ }
453
+ `;
454
+ const output = parser.parse(input);
455
+ expect(output).toBe(`cond(sprite, () => h(Sprite))`);
456
+ });
457
+
458
+ test("should compile negative condition", () => {
459
+ const input = `
460
+ @if (!sprite) {
461
+ <Sprite />
462
+ }
463
+ `;
464
+ const output = parser.parse(input);
465
+ expect(output).toBe(`cond(computed(() => !sprite()), () => h(Sprite))`);
466
+ });
467
+
468
+ test("should compile negative condition with multiple condition", () => {
469
+ const input = `
470
+ @if (!sprite && other) {
471
+ <Sprite />
472
+ }
473
+ `;
474
+ const output = parser.parse(input);
475
+ expect(output).toBe(`cond(computed(() => !sprite() && other()), () => h(Sprite))`);
476
+ });
477
+
478
+ test("should compile negative condition with multiple condition (or)", () => {
479
+ const input = `
480
+ @if (!sprite || other) {
481
+ <Sprite />
482
+ }
483
+ `;
484
+ const output = parser.parse(input);
485
+ expect(output).toBe(`cond(computed(() => !sprite() || other()), () => h(Sprite))`);
486
+ });
487
+
227
488
  test("should compile condition when sprite is visible", () => {
228
489
  const input = `
229
490
  @if (sprite.visible) {
@@ -234,6 +495,16 @@ describe("Condition", () => {
234
495
  expect(output).toBe(`cond(sprite.visible, () => h(Sprite))`);
235
496
  });
236
497
 
498
+ test("should compile condition when function value", () => {
499
+ const input = `
500
+ @if (val()) {
501
+ <Sprite />
502
+ }
503
+ `;
504
+ const output = parser.parse(input);
505
+ expect(output).toBe(`cond(val(), () => h(Sprite))`);
506
+ });
507
+
237
508
  test("should compile condition for multiple sprites", () => {
238
509
  const input = `
239
510
  @if (sprite) {
@@ -308,7 +579,7 @@ describe("Condition in Loops", () => {
308
579
  `;
309
580
  const output = parser.parse(input);
310
581
  expect(output.replace(/\s+/g, "")).toBe(
311
- `h(Canvas,null,loop(sprites,(sprite)=>cond(sprite.visible,()=>h(Sprite))))`.replace(/\s+/g, "")
582
+ `h(Canvas,null,loop(sprites,sprite=>cond(sprite.visible,()=>h(Sprite))))`.replace(/\s+/g, "")
312
583
  );
313
584
  });
314
585
 
@@ -322,7 +593,7 @@ describe("Condition in Loops", () => {
322
593
  `;
323
594
  const output = parser.parse(input);
324
595
  expect(output.replace(/\s+/g, "")).toBe(
325
- `h(Canvas,null,loop(sprites,(sprite)=>h(Sprite)))`.replace(/\s+/g, "")
596
+ `h(Canvas,null,loop(sprites,sprite=>h(Sprite)))`.replace(/\s+/g, "")
326
597
  );
327
598
  });
328
599
 
@@ -339,8 +610,282 @@ describe("Condition in Loops", () => {
339
610
  `;
340
611
  const output = parser.parse(input);
341
612
  expect(output.replace(/\s+/g, "")).toBe(
342
- `h(Canvas,null,[loop(sprites,(sprite)=>h(Sprite)),loop(others,(other)=>h(Sprite))])`.replace(/\s+/g, "")
613
+ `h(Canvas,null,[loop(sprites,sprite=>h(Sprite)),loop(others,other=>h(Sprite))])`.replace(/\s+/g, "")
614
+ );
615
+ });
616
+
617
+ });
618
+
619
+ describe('Svg', () => {
620
+ test('should compile svg', () => {
621
+ const input = `<svg>
622
+ <path d="M 100 350 l 150 -300" stroke="red" stroke-width="4"/>
623
+ </svg>`;
624
+ const output = parser.parse(input);
625
+ expect(output).toBe('h(Svg, { content: `<svg><path d="M 100 350 l 150 -300" stroke="red" stroke-width="4"/></svg>` })');
626
+ });
627
+
628
+ test('should compile svg with canvas', () => {
629
+ const input = `<Canvas antialias={true}>
630
+ <svg height="400" width="450" xmlns="http://www.w3.org/2000/svg"></svg></Canvas>`;
631
+ const output = parser.parse(input);
632
+ expect(output).toBe('h(Canvas, { antialias: true }, h(Svg, { content: `<svg height="400" width="450" xmlns="http://www.w3.org/2000/svg"></svg>` }))');
633
+ });
634
+ });
635
+
636
+ describe('DOM', () => {
637
+ test('should compile input DOM', () => {
638
+ const input = `<input type="text" />`;
639
+ const output = parser.parse(input);
640
+ expect(output).toBe('h(DOMContainer, { element: "input", attrs: { type: \'text\' } })');
641
+ });
642
+
643
+ test('should compile div DOM', () => {
644
+ const input = `<div class="container" />`;
645
+ const output = parser.parse(input);
646
+ expect(output).toBe('h(DOMContainer, { element: "div", attrs: { class: \'container\' } })');
647
+ });
648
+
649
+ test('should compile button DOM', () => {
650
+ const input = `<button type="submit" />`;
651
+ const output = parser.parse(input);
652
+ expect(output).toBe('h(DOMContainer, { element: "button", attrs: { type: \'submit\' } })');
653
+ });
654
+
655
+ test('should compile button DOM', () => {
656
+ const input = `<button>Text</button>`;
657
+ const output = parser.parse(input);
658
+ expect(output).toBe('h(DOMContainer, { element: "button", textContent: \'Text\' })');
659
+ });
660
+
661
+ test('should compile textarea DOM with dynamic attributes', () => {
662
+ const input = `<textarea rows={rows} cols={cols} />`;
663
+ const output = parser.parse(input);
664
+ expect(output).toBe('h(DOMContainer, { element: "textarea", attrs: { rows: rows, cols: cols } })');
665
+ });
666
+
667
+ test('should not transform Canvas to DOM', () => {
668
+ const input = `<Canvas width={800} />`;
669
+ const output = parser.parse(input);
670
+ expect(output).toBe('h(Canvas, { width: 800 })');
671
+ });
672
+
673
+ test('should not transform Sprite to DOM', () => {
674
+ const input = `<Sprite image="test.png" />`;
675
+ const output = parser.parse(input);
676
+ expect(output).toBe('h(Sprite, { image: \'test.png\' })');
677
+ });
678
+
679
+ // Tests avec imbrications DOM
680
+ test('should compile nested DOM elements', () => {
681
+ const input = `<div class="container">
682
+ <p>Hello World</p>
683
+ </div>`;
684
+ const output = parser.parse(input);
685
+ expect(output).toBe('h(DOMContainer, { element: "div", attrs: { class: \'container\' } }, h(DOMContainer, { element: "p", textContent: \'Hello World\' }))');
686
+ });
687
+
688
+ test('should compile deeply nested DOM elements', () => {
689
+ const input = `<div class="wrapper">
690
+ <section>
691
+ <h1>Title</h1>
692
+ <p>Content</p>
693
+ </section>
694
+ </div>`;
695
+ const output = parser.parse(input);
696
+ expect(output.replace(/\s+/g, "")).toBe(
697
+ `h(DOMContainer, { element: "div", attrs: { class: 'wrapper' } }, h(DOMContainer, { element: "section" }, [h(DOMContainer, { element: "h1", textContent: 'Title' }), h(DOMContainer, { element: "p", textContent: 'Content' })]))`.replace(/\s+/g, "")
343
698
  );
344
699
  });
345
700
 
701
+ test('should compile DOM with multiple children', () => {
702
+ const input = `<ul>
703
+ <li>Item 1</li>
704
+ <li>Item 2</li>
705
+ <li>Item 3</li>
706
+ </ul>`;
707
+ const output = parser.parse(input);
708
+ expect(output.replace(/\s+/g, "")).toBe(
709
+ `h(DOMContainer, { element: "ul" }, [h(DOMContainer, { element: "li", textContent: 'Item 1' }), h(DOMContainer, { element: "li", textContent: 'Item 2' }), h(DOMContainer, { element: "li", textContent: 'Item 3' })])`.replace(/\s+/g, "")
710
+ );
711
+ });
712
+
713
+ test('should compile mixed DOM and framework components', () => {
714
+ const input = `<div class="game-container">
715
+ <Canvas width={800} height={600} />
716
+ <div class="ui">
717
+ <button>Start Game</button>
718
+ </div>
719
+ </div>`;
720
+ const output = parser.parse(input);
721
+ expect(output.replace(/\s+/g, "")).toBe(
722
+ `h(DOMContainer, { element: "div", attrs: { class: 'game-container' } }, [h(Canvas, { width: 800, height: 600 }), h(DOMContainer, { element: "div", attrs: { class: 'ui' } }, h(DOMContainer, { element: "button", textContent: 'Start Game' }))])`.replace(/\s+/g, "")
723
+ );
724
+ });
725
+
726
+ test('should compile DOM with attributes and text content', () => {
727
+ const input = `<button class="btn primary" type="submit">Submit Form</button>`;
728
+ const output = parser.parse(input);
729
+ expect(output).toBe('h(DOMContainer, { element: "button", attrs: { class: \'btn primary\', type: \'submit\' }, textContent: \'Submit Form\' })');
730
+ });
731
+ });
732
+
733
+ describe('DOM with special attributes', () => {
734
+ test('should compile DOM with special attributes', () => {
735
+ const input = `<input type="password" x={100} y={100} />`;
736
+ const output = parser.parse(input);
737
+ expect(output).toBe('h(DOMContainer, { element: "input", attrs: { type: \'password\' }, x: 100, y: 100 })');
738
+ });
346
739
  });
740
+
741
+ describe('DOM with Control Structures', () => {
742
+ test('should compile @for loop with DOM elements', () => {
743
+ const input = `
744
+ @for (item of items) {
745
+ <li>{item.name}</li>
746
+ }
747
+ `;
748
+ const output = parser.parse(input);
749
+ expect(output.replace(/\s+/g, "")).toBe(
750
+ `loop(items, item => h(DOMContainer, { element: "li", textContent: computed(() => item().name()) }))`.replace(/\s+/g, "")
751
+ );
752
+ });
753
+
754
+ test('should compile @for loop with nested DOM structure', () => {
755
+ const input = `
756
+ <ul class="menu">
757
+ @for (item of menuItems) {
758
+ <li class="menu-item">
759
+ <a href={item.url}>{item.title}</a>
760
+ </li>
761
+ }
762
+ </ul>
763
+ `;
764
+ const output = parser.parse(input);
765
+ expect(output.replace(/\s+/g, "")).toBe(
766
+ `h(DOMContainer, { element: "ul", attrs: { class: 'menu' } }, loop(menuItems, item => h(DOMContainer, { element: "li", attrs: { class: 'menu-item' } }, h(DOMContainer, { element: "a", attrs: { href: computed(() => item().url()) }, textContent: computed(() => item().title()) }))))`.replace(/\s+/g, "")
767
+ );
768
+ });
769
+
770
+ test('should compile @if condition with DOM elements', () => {
771
+ const input = `
772
+ @if (showMessage) {
773
+ <div class="alert">
774
+ <p>Important message!</p>
775
+ </div>
776
+ }
777
+ `;
778
+ const output = parser.parse(input);
779
+ expect(output.replace(/\s+/g, "")).toBe(
780
+ `cond(showMessage, () => h(DOMContainer, { element: "div", attrs: { class: 'alert' } }, h(DOMContainer, { element: "p", textContent: 'Important message!' })))`.replace(/\s+/g, "")
781
+ );
782
+ });
783
+
784
+ test('should compile @if condition with simple DOM element', () => {
785
+ const input = `
786
+ @if (isVisible) {
787
+ <button>Click me</button>
788
+ }
789
+ `;
790
+ const output = parser.parse(input);
791
+ expect(output.replace(/\s+/g, "")).toBe(
792
+ `cond(isVisible, () => h(DOMContainer, { element: "button", textContent: 'Click me' }))`.replace(/\s+/g, "")
793
+ );
794
+ });
795
+
796
+ test('should compile nested @for and @if with DOM', () => {
797
+ const input = `
798
+ <div class="container">
799
+ @for (section of sections) {
800
+ @if (section.visible) {
801
+ <section class="content">
802
+ <h2>{section.title}</h2>
803
+ <div class="items">
804
+ @for (item of section.items) {
805
+ <div class="item">{item.name}</div>
806
+ }
807
+ </div>
808
+ </section>
809
+ }
810
+ }
811
+ </div>
812
+ `;
813
+ const output = parser.parse(input);
814
+ expect(output.replace(/\s+/g, "")).toBe(
815
+ `h(DOMContainer, { element: "div", attrs: { class: 'container' } }, loop(sections, section => cond(section.visible, () => h(DOMContainer, { element: "section", attrs: { class: 'content' } }, [h(DOMContainer, { element: "h2", textContent: computed(() => section().title()) }), h(DOMContainer, { element: "div", attrs: { class: 'items' } }, loop(section.items, item => h(DOMContainer, { element: "div", attrs: { class: 'item' }, textContent: computed(() => item().name()) })))]))))`.replace(/\s+/g, "")
816
+ );
817
+ });
818
+
819
+ test('should compile @for with DOM table structure', () => {
820
+ const input = `
821
+ <table>
822
+ <thead>
823
+ <tr>
824
+ <th>Name</th>
825
+ <th>Age</th>
826
+ </tr>
827
+ </thead>
828
+ <tbody>
829
+ @for (user of users) {
830
+ <tr>
831
+ <td>{user.name}</td>
832
+ <td>{user.age}</td>
833
+ </tr>
834
+ }
835
+ </tbody>
836
+ </table>
837
+ `;
838
+ const output = parser.parse(input);
839
+ expect(output.replace(/\s+/g, "")).toBe(
840
+ `h(DOMContainer, { element: "table" }, [h(DOMContainer, { element: "thead" }, h(DOMContainer, { element: "tr" }, [h(DOMContainer, { element: "th", textContent: 'Name' }), h(DOMContainer, { element: "th", textContent: 'Age' })])), h(DOMContainer, { element: "tbody" }, loop(users, user => h(DOMContainer, { element: "tr" }, [h(DOMContainer, { element: "td", textContent: computed(() => user().name()) }), h(DOMContainer, { element: "td", textContent: computed(() => user().age()) })])))])`.replace(/\s+/g, "")
841
+ );
842
+ });
843
+
844
+ test('should compile @if with multiple DOM conditions', () => {
845
+ const input = `
846
+ <div class="status">
847
+ @if (isLoading) {
848
+ <div class="spinner">Loading...</div>
849
+ }
850
+ @if (hasError) {
851
+ <div class="error">Error occurred!</div>
852
+ }
853
+ @if (isSuccess) {
854
+ <div class="success">Success!</div>
855
+ }
856
+ </div>
857
+ `;
858
+ const output = parser.parse(input);
859
+ expect(output.replace(/\s+/g, "")).toBe(
860
+ `h(DOMContainer, { element: "div", attrs: { class: 'status' } }, [cond(isLoading, () => h(DOMContainer, { element: "div", attrs: { class: 'spinner' }, textContent: 'Loading...' })), cond(hasError, () => h(DOMContainer, { element: "div", attrs: { class: 'error' }, textContent: 'Error occurred!' })), cond(isSuccess, () => h(DOMContainer, { element: "div", attrs: { class: 'success' }, textContent: 'Success!' }))])`.replace(/\s+/g, "")
861
+ );
862
+ });
863
+
864
+ test('should compile mixed Canvas and DOM with control structures', () => {
865
+ const input = `
866
+ <div class="game-wrapper">
867
+ <Canvas width={800} height={600}>
868
+ @for (sprite of sprites) {
869
+ <Sprite x={sprite.x} y={sprite.y} />
870
+ }
871
+ </Canvas>
872
+ <div class="ui-overlay">
873
+ @if (showScore) {
874
+ <div class="score">Score: {score}</div>
875
+ }
876
+ @if (showMenu) {
877
+ <div class="menu">
878
+ @for (option of menuOptions) {
879
+ <button class="menu-btn">{option.label}</button>
880
+ }
881
+ </div>
882
+ }
883
+ </div>
884
+ </div>
885
+ `;
886
+ const output = parser.parse(input);
887
+ expect(output.replace(/\s+/g, "")).toBe(
888
+ `h(DOMContainer, { 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(DOMContainer, { element: "div", attrs: { class: 'ui-overlay' } }, [cond(showScore, () => h(DOMContainer, { element: "div", attrs: { class: 'score' }, textContent: computed(() => 'Score: ' + computed(() => score())) })), cond(showMenu, () => h(DOMContainer, { element: "div", attrs: { class: 'menu' } }, loop(menuOptions, option => h(DOMContainer, { element: "button", attrs: { class: 'menu-btn' }, textContent: computed(() => option().label()) }))))])])`.replace(/\s+/g, "")
889
+ );
890
+ });
891
+ });