@nordcraft/ssr 1.0.96 → 2.0.0

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.
Files changed (34) hide show
  1. package/dist/components/utils.test.d.ts +1 -0
  2. package/dist/components/utils.test.js +90 -0
  3. package/dist/components/utils.test.js.map +1 -0
  4. package/dist/rendering/components.test.d.ts +1 -0
  5. package/dist/rendering/components.test.js +903 -0
  6. package/dist/rendering/components.test.js.map +1 -0
  7. package/dist/rendering/formulaContext.test.d.ts +1 -0
  8. package/dist/rendering/formulaContext.test.js +93 -0
  9. package/dist/rendering/formulaContext.test.js.map +1 -0
  10. package/dist/rendering/template.test.d.ts +1 -0
  11. package/dist/rendering/template.test.js +16 -0
  12. package/dist/rendering/template.test.js.map +1 -0
  13. package/dist/rendering/testData.js +1 -1
  14. package/dist/rendering/testData.js.map +1 -1
  15. package/dist/rendering/testData.test.d.ts +1 -0
  16. package/dist/rendering/testData.test.js +461 -0
  17. package/dist/rendering/testData.test.js.map +1 -0
  18. package/dist/routing/routing.js +1 -8
  19. package/dist/routing/routing.js.map +1 -1
  20. package/dist/routing/routing.test.d.ts +1 -0
  21. package/dist/routing/routing.test.js +415 -0
  22. package/dist/routing/routing.test.js.map +1 -0
  23. package/dist/ssr.types.d.ts +1 -1
  24. package/dist/utils/media.test.d.ts +1 -0
  25. package/dist/utils/media.test.js +123 -0
  26. package/dist/utils/media.test.js.map +1 -0
  27. package/dist/utils/routes.d.ts +1 -1
  28. package/package.json +3 -3
  29. package/src/rendering/testData.test.ts +1 -1
  30. package/src/rendering/testData.ts +4 -1
  31. package/src/routing/routing.test.ts +165 -4
  32. package/src/routing/routing.ts +1 -11
  33. package/src/ssr.types.ts +1 -1
  34. package/src/utils/media.test.ts +3 -3
@@ -0,0 +1,903 @@
1
+ import { functionFormula, valueFormula, } from '@nordcraft/core/dist/formula/formulaUtils';
2
+ import { describe, expect, test } from 'bun:test';
3
+ import { renderPageBody } from './components';
4
+ import { getPageFormulaContext } from './formulaContext';
5
+ describe('renderPageBody', () => {
6
+ test('should render a simple page', async () => {
7
+ const component = {
8
+ name: 'SimpleComponent',
9
+ contexts: {},
10
+ route: {
11
+ path: [
12
+ {
13
+ name: 'home',
14
+ type: 'static',
15
+ },
16
+ ],
17
+ query: {},
18
+ },
19
+ events: [],
20
+ nodes: {
21
+ root: {
22
+ tag: 'div',
23
+ type: 'element',
24
+ attrs: {
25
+ 'data-test-attr': {
26
+ type: 'value',
27
+ value: 'test',
28
+ },
29
+ },
30
+ style: { color: 'red' },
31
+ events: {},
32
+ classes: {},
33
+ children: [],
34
+ },
35
+ },
36
+ formulas: {},
37
+ apis: {},
38
+ attributes: {},
39
+ variables: {},
40
+ };
41
+ const { html } = await renderPageBody({
42
+ evaluateComponentApis: () => ({}),
43
+ component: component,
44
+ formulaContext: {
45
+ data: {
46
+ Attributes: {},
47
+ },
48
+ component,
49
+ env: {},
50
+ package: undefined,
51
+ toddle: {},
52
+ },
53
+ env: {},
54
+ files: { components: { SimpleComponent: component } },
55
+ includedComponents: [component],
56
+ projectId: 'test-project',
57
+ req: {},
58
+ });
59
+ expect(html).toBe('<div data-test-attr="test" data-id="0" data-node-id="root" class="cTNpFk"></div>');
60
+ });
61
+ test('should render a static value provided by a context provider component', async () => {
62
+ const providerComponent = {
63
+ name: 'ProviderComponent',
64
+ formulas: {
65
+ contextValue: {
66
+ name: 'contextValue',
67
+ exposeInContext: true,
68
+ formula: {
69
+ type: 'value',
70
+ value: 'provided-value',
71
+ },
72
+ },
73
+ },
74
+ nodes: {
75
+ root: {
76
+ type: 'component',
77
+ name: 'ConsumerComponent',
78
+ attrs: {},
79
+ children: [],
80
+ events: {},
81
+ style: {},
82
+ },
83
+ },
84
+ };
85
+ const consumerComponent = {
86
+ name: 'ConsumerComponent',
87
+ contexts: {
88
+ ProviderComponent: {
89
+ formulas: ['contextValue'],
90
+ workflows: [],
91
+ },
92
+ },
93
+ nodes: {
94
+ root: {
95
+ type: 'text',
96
+ value: {
97
+ type: 'path',
98
+ path: ['Contexts', 'ProviderComponent', 'contextValue'],
99
+ },
100
+ },
101
+ },
102
+ formulas: {},
103
+ apis: {},
104
+ attributes: {},
105
+ variables: {},
106
+ events: [],
107
+ };
108
+ const { html } = await renderPageBody({
109
+ evaluateComponentApis: () => ({}),
110
+ component: providerComponent,
111
+ formulaContext: {
112
+ data: {
113
+ Attributes: {},
114
+ },
115
+ component: providerComponent,
116
+ env: {},
117
+ package: undefined,
118
+ toddle: {},
119
+ },
120
+ env: {},
121
+ files: {
122
+ components: {
123
+ ProviderComponent: providerComponent,
124
+ ConsumerComponent: consumerComponent,
125
+ },
126
+ },
127
+ includedComponents: [providerComponent, consumerComponent],
128
+ projectId: 'test-project',
129
+ req: {},
130
+ });
131
+ expect(html).toBe('<span data-node-type="text" data-node-id="root">provided-value</span>');
132
+ });
133
+ // Bug: https://discord.com/channels/972416966683926538/1458386701612351612/1458386701612351612
134
+ test('should render correct style overrides when component is used through packages', async () => {
135
+ const buttonComponent = {
136
+ name: 'ButtonComponent',
137
+ nodes: {
138
+ root: {
139
+ type: 'element',
140
+ tag: 'button',
141
+ attrs: {},
142
+ style: {
143
+ backgroundColor: 'blue',
144
+ },
145
+ events: {},
146
+ classes: {},
147
+ children: [],
148
+ },
149
+ },
150
+ formulas: {},
151
+ apis: {},
152
+ attributes: {},
153
+ variables: {},
154
+ events: [],
155
+ };
156
+ const buttonWrapperComponent = {
157
+ name: 'ButtonWrapperComponent',
158
+ nodes: {
159
+ root: {
160
+ type: 'component',
161
+ name: 'ButtonComponent',
162
+ attrs: {},
163
+ children: [],
164
+ events: {},
165
+ style: {
166
+ backgroundColor: 'red',
167
+ },
168
+ },
169
+ },
170
+ formulas: {},
171
+ apis: {},
172
+ attributes: {},
173
+ variables: {},
174
+ events: [],
175
+ };
176
+ const pageComponent = {
177
+ name: 'PageComponent',
178
+ nodes: {
179
+ root: {
180
+ type: 'component',
181
+ name: 'ButtonWrapperComponent',
182
+ attrs: {},
183
+ children: [],
184
+ events: {},
185
+ style: {},
186
+ package: 'test-package',
187
+ },
188
+ },
189
+ formulas: {},
190
+ apis: {},
191
+ attributes: {},
192
+ variables: {},
193
+ events: [],
194
+ };
195
+ const { html } = await renderPageBody({
196
+ evaluateComponentApis: () => ({}),
197
+ component: pageComponent,
198
+ formulaContext: {
199
+ data: {
200
+ Attributes: {},
201
+ },
202
+ component: pageComponent,
203
+ env: {},
204
+ package: undefined,
205
+ toddle: {},
206
+ },
207
+ env: {},
208
+ files: {
209
+ components: {
210
+ PageComponent: pageComponent,
211
+ },
212
+ packages: {
213
+ 'test-package': {
214
+ components: {
215
+ ButtonWrapperComponent: buttonWrapperComponent,
216
+ ButtonComponent: buttonComponent,
217
+ },
218
+ },
219
+ },
220
+ },
221
+ includedComponents: [
222
+ pageComponent,
223
+ buttonWrapperComponent,
224
+ buttonComponent,
225
+ ],
226
+ projectId: 'test-project',
227
+ req: {},
228
+ });
229
+ // Own styling
230
+ expect(html).toContain('PageComponent:root');
231
+ // Package component styling override
232
+ expect(html).toContain('test-package/ButtonWrapperComponent:root');
233
+ });
234
+ // Bug: https://discord.com/channels/972416966683926538/1471073500520386760
235
+ test('should evaluate project formulas correctly inside package components', async () => {
236
+ const packageWrapperComponent = {
237
+ name: 'PackageWrapperComponent',
238
+ nodes: {
239
+ root: {
240
+ type: 'element',
241
+ tag: 'span',
242
+ attrs: {},
243
+ children: ['childText'],
244
+ events: {},
245
+ },
246
+ childText: {
247
+ type: 'slot',
248
+ children: [],
249
+ },
250
+ },
251
+ };
252
+ const pageComponent = {
253
+ name: 'PageComponent',
254
+ route: {
255
+ path: [],
256
+ query: {},
257
+ },
258
+ nodes: {
259
+ root: {
260
+ type: 'element',
261
+ tag: 'div',
262
+ attrs: {},
263
+ children: ['firstChild', 'secondChild'],
264
+ events: {},
265
+ style: {},
266
+ },
267
+ firstChild: {
268
+ type: 'text',
269
+ value: functionFormula('test-formula'),
270
+ },
271
+ secondChild: {
272
+ type: 'component',
273
+ name: 'PackageWrapperComponent',
274
+ package: 'test-package',
275
+ attrs: {},
276
+ children: ['nestedChild'],
277
+ events: {},
278
+ },
279
+ nestedChild: {
280
+ type: 'text',
281
+ value: functionFormula('test-formula'),
282
+ },
283
+ },
284
+ formulas: {},
285
+ apis: {},
286
+ attributes: {},
287
+ variables: {},
288
+ events: [],
289
+ };
290
+ const files = {
291
+ components: {
292
+ PageComponent: pageComponent,
293
+ },
294
+ formulas: {
295
+ 'test-formula': {
296
+ name: 'test-formula',
297
+ formula: valueFormula('output-from-test-formula'),
298
+ version: 2,
299
+ arguments: [],
300
+ },
301
+ },
302
+ packages: {
303
+ 'test-package': {
304
+ manifest: {
305
+ name: 'test-package',
306
+ commit: '123',
307
+ },
308
+ components: {
309
+ PackageWrapperComponent: packageWrapperComponent,
310
+ },
311
+ },
312
+ },
313
+ };
314
+ const formulaContext = getPageFormulaContext({
315
+ component: pageComponent,
316
+ branchName: 'main',
317
+ req: new Request('http://localhost'),
318
+ logErrors: true,
319
+ files,
320
+ });
321
+ const { html } = await renderPageBody({
322
+ evaluateComponentApis: () => ({}),
323
+ component: pageComponent,
324
+ formulaContext,
325
+ env: formulaContext.env,
326
+ files,
327
+ includedComponents: [pageComponent, packageWrapperComponent],
328
+ projectId: 'test-project',
329
+ req: {},
330
+ });
331
+ // Expect the first child text node to render the formula value
332
+ expect(html).toInclude('<span data-node-type="text" data-node-id="firstChild">output-from-test-formula</span>');
333
+ // Expect the nested child text node inside the package component to also render the formula value,
334
+ // proving that the formula is evaluated correctly even when used inside a package component
335
+ expect(html).toInclude('<span data-id="0.1" data-node-id="root" class="cYXIdv PageComponent:secondChild"><span data-node-type="text" data-node-id="nestedChild">output-from-test-formula</span></span>');
336
+ });
337
+ // Bug: https://discord.com/channels/972416966683926538/1458387768504746068/1458387768504746068
338
+ // TODO: Fix rendering so that context is properly passed through slots. Rendering should go through inner components before slotted content.
339
+ test('should render context value when consumer is through multiple slots of a wrapped provider', async () => {
340
+ // Four components required for test setup:
341
+ // PageComponent - uses WrappedProviderComponent with a slotted ConsumerComponent
342
+ // WrappedProviderComponent - uses ProviderComponent and passes slot to it
343
+ // ProviderComponent - provides context value to its children through a slot
344
+ // ConsumerComponent - consumes context value and renders it
345
+ const pageComponent = {
346
+ name: 'PageComponent',
347
+ nodes: {
348
+ root: {
349
+ type: 'component',
350
+ name: 'WrappedProviderComponent',
351
+ attrs: {},
352
+ children: ['consumerSlot'],
353
+ events: {},
354
+ style: {},
355
+ },
356
+ consumerSlot: {
357
+ type: 'component',
358
+ name: 'ConsumerComponent',
359
+ attrs: {},
360
+ children: [],
361
+ events: {},
362
+ style: {},
363
+ },
364
+ },
365
+ formulas: {},
366
+ apis: {},
367
+ attributes: {},
368
+ variables: {},
369
+ events: [],
370
+ };
371
+ const wrappedProviderComponent = {
372
+ name: 'WrappedProviderComponent',
373
+ nodes: {
374
+ root: {
375
+ type: 'component',
376
+ name: 'ProviderComponent',
377
+ attrs: {},
378
+ children: ['slot'],
379
+ events: {},
380
+ style: {},
381
+ },
382
+ slot: {
383
+ type: 'slot',
384
+ children: [],
385
+ },
386
+ },
387
+ formulas: {},
388
+ apis: {},
389
+ attributes: {},
390
+ variables: {},
391
+ events: [],
392
+ };
393
+ const providerComponent = {
394
+ name: 'ProviderComponent',
395
+ formulas: {
396
+ contextValue: {
397
+ name: 'contextValue',
398
+ exposeInContext: true,
399
+ formula: {
400
+ type: 'value',
401
+ value: 'provided-value',
402
+ },
403
+ },
404
+ },
405
+ nodes: {
406
+ root: {
407
+ type: 'slot',
408
+ children: [],
409
+ },
410
+ },
411
+ };
412
+ const consumerComponent = {
413
+ name: 'ConsumerComponent',
414
+ contexts: {
415
+ ProviderComponent: {
416
+ formulas: ['contextValue'],
417
+ workflows: [],
418
+ },
419
+ },
420
+ nodes: {
421
+ root: {
422
+ type: 'text',
423
+ value: {
424
+ type: 'path',
425
+ path: ['Contexts', 'ProviderComponent', 'contextValue'],
426
+ },
427
+ },
428
+ },
429
+ formulas: {},
430
+ apis: {},
431
+ attributes: {},
432
+ variables: {},
433
+ events: [],
434
+ };
435
+ const { html } = await renderPageBody({
436
+ evaluateComponentApis: () => ({}),
437
+ component: pageComponent,
438
+ formulaContext: {
439
+ data: {
440
+ Attributes: {},
441
+ },
442
+ component: pageComponent,
443
+ env: {},
444
+ package: undefined,
445
+ toddle: {},
446
+ },
447
+ env: {},
448
+ files: {
449
+ components: {
450
+ PageComponent: pageComponent,
451
+ WrappedProviderComponent: wrappedProviderComponent,
452
+ ProviderComponent: providerComponent,
453
+ ConsumerComponent: consumerComponent,
454
+ },
455
+ },
456
+ includedComponents: [
457
+ pageComponent,
458
+ wrappedProviderComponent,
459
+ providerComponent,
460
+ consumerComponent,
461
+ ],
462
+ projectId: 'test-project',
463
+ req: {},
464
+ });
465
+ expect(html).toBe(`<span data-node-type="text" data-node-id="root">provided-value</span>`);
466
+ });
467
+ test('should render a component where a variable initial value references Page.Theme', async () => {
468
+ const component = {
469
+ name: 'ThemeVariableComponent',
470
+ route: {
471
+ path: [{ name: 'home', type: 'static' }],
472
+ query: {},
473
+ info: {
474
+ theme: {
475
+ formula: {
476
+ type: 'value',
477
+ value: 'dark',
478
+ },
479
+ },
480
+ },
481
+ },
482
+ variables: {
483
+ themeVar: {
484
+ initialValue: {
485
+ type: 'path',
486
+ path: ['Page', 'Theme'],
487
+ },
488
+ },
489
+ },
490
+ nodes: {
491
+ root: {
492
+ type: 'text',
493
+ value: {
494
+ type: 'path',
495
+ path: ['Variables', 'themeVar'],
496
+ },
497
+ },
498
+ },
499
+ formulas: {},
500
+ apis: {},
501
+ attributes: {},
502
+ events: [],
503
+ };
504
+ const { html } = await renderPageBody({
505
+ evaluateComponentApis: () => ({}),
506
+ component: component,
507
+ formulaContext: getPageFormulaContext({
508
+ component: component,
509
+ branchName: 'main',
510
+ req: new Request('http://localhost'),
511
+ logErrors: true,
512
+ files: {
513
+ components: { ThemeVariableComponent: component },
514
+ },
515
+ }),
516
+ env: {},
517
+ files: { components: { ThemeVariableComponent: component } },
518
+ includedComponents: [component],
519
+ projectId: 'test-project',
520
+ req: {},
521
+ });
522
+ expect(html).toBe('<span data-node-type="text" data-node-id="root">dark</span>');
523
+ });
524
+ test('should render a component where Page.Theme references a variables initial value', async () => {
525
+ const component = {
526
+ name: 'ThemeVariableComponent',
527
+ route: {
528
+ path: [{ name: 'home', type: 'static' }],
529
+ query: {},
530
+ info: {
531
+ theme: {
532
+ formula: {
533
+ type: 'path',
534
+ path: ['Variables', 'themeVar'],
535
+ },
536
+ },
537
+ },
538
+ },
539
+ variables: {
540
+ themeVar: {
541
+ initialValue: {
542
+ type: 'value',
543
+ value: 'hotdog',
544
+ },
545
+ },
546
+ },
547
+ nodes: {
548
+ root: {
549
+ type: 'text',
550
+ value: {
551
+ type: 'path',
552
+ path: ['Page', 'Theme'],
553
+ },
554
+ },
555
+ },
556
+ formulas: {},
557
+ apis: {},
558
+ attributes: {},
559
+ events: [],
560
+ };
561
+ const { html } = await renderPageBody({
562
+ evaluateComponentApis: () => ({}),
563
+ component: component,
564
+ formulaContext: getPageFormulaContext({
565
+ component: component,
566
+ branchName: 'main',
567
+ req: new Request('http://localhost'),
568
+ logErrors: true,
569
+ files: {
570
+ components: { ThemeVariableComponent: component },
571
+ },
572
+ }),
573
+ env: {},
574
+ files: { components: { ThemeVariableComponent: component } },
575
+ includedComponents: [component],
576
+ projectId: 'test-project',
577
+ req: {},
578
+ });
579
+ expect(html).toBe('<span data-node-type="text" data-node-id="root">hotdog</span>');
580
+ });
581
+ test('should render a child component where a variable initial value references Page.Theme from the parent', async () => {
582
+ const childComponent = {
583
+ name: 'ChildComponent',
584
+ variables: {
585
+ childThemeVar: {
586
+ initialValue: {
587
+ type: 'path',
588
+ path: ['Page', 'Theme'],
589
+ },
590
+ },
591
+ },
592
+ nodes: {
593
+ root: {
594
+ type: 'text',
595
+ value: {
596
+ type: 'path',
597
+ path: ['Variables', 'childThemeVar'],
598
+ },
599
+ },
600
+ },
601
+ formulas: {},
602
+ apis: {},
603
+ attributes: {},
604
+ events: [],
605
+ };
606
+ const parentComponent = {
607
+ name: 'ParentComponent',
608
+ route: {
609
+ path: [{ name: 'home', type: 'static' }],
610
+ query: {},
611
+ },
612
+ nodes: {
613
+ root: {
614
+ type: 'component',
615
+ name: 'ChildComponent',
616
+ attrs: {},
617
+ children: [],
618
+ events: {},
619
+ style: {},
620
+ },
621
+ },
622
+ formulas: {},
623
+ apis: {},
624
+ attributes: {},
625
+ variables: {},
626
+ events: [],
627
+ };
628
+ const { html } = await renderPageBody({
629
+ evaluateComponentApis: () => ({}),
630
+ component: parentComponent,
631
+ formulaContext: {
632
+ data: {
633
+ Attributes: {},
634
+ Page: {
635
+ Theme: 'light',
636
+ },
637
+ },
638
+ component: parentComponent,
639
+ env: {},
640
+ package: undefined,
641
+ toddle: {},
642
+ },
643
+ env: {},
644
+ files: {
645
+ components: {
646
+ ParentComponent: parentComponent,
647
+ ChildComponent: childComponent,
648
+ },
649
+ },
650
+ includedComponents: [parentComponent, childComponent],
651
+ projectId: 'test-project',
652
+ req: {},
653
+ });
654
+ expect(html).toBe('<span data-node-type="text" data-node-id="root">light</span>');
655
+ });
656
+ test('Component C should see the context value from Component A when Component B forwards its attribute using slots', async () => {
657
+ // Component A: Provider
658
+ // Accepts 'value' as attribute and provides it in context
659
+ const componentA = {
660
+ name: 'ComponentA',
661
+ attributes: {
662
+ value: {
663
+ name: 'value',
664
+ testValue: 'test',
665
+ },
666
+ },
667
+ formulas: {
668
+ exposedValue: {
669
+ name: 'exposedValue',
670
+ exposeInContext: true,
671
+ formula: {
672
+ type: 'path',
673
+ path: ['Attributes', 'value'],
674
+ },
675
+ },
676
+ },
677
+ nodes: {
678
+ root: {
679
+ type: 'element',
680
+ tag: 'div',
681
+ attrs: {},
682
+ children: ['slot_node'],
683
+ events: {},
684
+ style: {},
685
+ },
686
+ slot_node: {
687
+ type: 'slot',
688
+ name: 'default',
689
+ children: [],
690
+ },
691
+ },
692
+ contexts: {},
693
+ apis: {},
694
+ variables: {},
695
+ events: [],
696
+ };
697
+ // Component B: Intermediary
698
+ // Accepts 'val' as attribute and forwards it to Component A's 'value' attribute
699
+ const componentB = {
700
+ name: 'ComponentB',
701
+ attributes: {
702
+ val: {
703
+ name: 'val',
704
+ testValue: 'test',
705
+ },
706
+ },
707
+ nodes: {
708
+ root: {
709
+ type: 'element',
710
+ tag: 'div',
711
+ attrs: {},
712
+ children: ['provider_node'],
713
+ events: {},
714
+ style: {},
715
+ },
716
+ provider_node: {
717
+ type: 'component',
718
+ name: 'ComponentA',
719
+ attrs: {
720
+ value: {
721
+ type: 'path',
722
+ path: ['Attributes', 'val'],
723
+ },
724
+ },
725
+ children: ['slot_forwarder'],
726
+ events: {},
727
+ style: {},
728
+ },
729
+ slot_forwarder: {
730
+ type: 'slot',
731
+ name: 'default',
732
+ children: [],
733
+ },
734
+ },
735
+ contexts: {},
736
+ formulas: {},
737
+ apis: {},
738
+ variables: {},
739
+ events: [],
740
+ };
741
+ // Component C: Consumer
742
+ // Consumes Component A's context 'exposedValue'
743
+ const componentC = {
744
+ name: 'ComponentC',
745
+ contexts: {
746
+ ComponentA: {
747
+ formulas: ['exposedValue'],
748
+ workflows: [],
749
+ },
750
+ },
751
+ nodes: {
752
+ root: {
753
+ type: 'text',
754
+ value: {
755
+ type: 'path',
756
+ path: ['Contexts', 'ComponentA', 'exposedValue'],
757
+ },
758
+ },
759
+ },
760
+ formulas: {},
761
+ apis: {},
762
+ attributes: {},
763
+ variables: {},
764
+ events: [],
765
+ };
766
+ // Root component that renders B with a specific value and C in its slot
767
+ const rootComponent = {
768
+ name: 'RootComponent',
769
+ route: {
770
+ path: [],
771
+ query: {},
772
+ info: {
773
+ theme: {
774
+ formula: { type: 'value', value: 'light' },
775
+ },
776
+ },
777
+ },
778
+ nodes: {
779
+ root: {
780
+ type: 'component',
781
+ name: 'ComponentB',
782
+ attrs: {
783
+ val: {
784
+ type: 'value',
785
+ value: 'expected-value',
786
+ },
787
+ },
788
+ children: ['consumer_node'],
789
+ events: {},
790
+ style: {},
791
+ },
792
+ consumer_node: {
793
+ type: 'component',
794
+ name: 'ComponentC',
795
+ attrs: {},
796
+ children: [],
797
+ events: {},
798
+ style: {},
799
+ slot: 'default',
800
+ },
801
+ },
802
+ contexts: {},
803
+ formulas: {},
804
+ apis: {},
805
+ attributes: {},
806
+ variables: {},
807
+ events: [],
808
+ };
809
+ const files = {
810
+ components: {
811
+ RootComponent: rootComponent,
812
+ ComponentB: componentB,
813
+ ComponentA: componentA,
814
+ ComponentC: componentC,
815
+ },
816
+ };
817
+ const { html } = await renderPageBody({
818
+ evaluateComponentApis: () => ({}),
819
+ component: rootComponent,
820
+ formulaContext: getPageFormulaContext({
821
+ component: rootComponent,
822
+ branchName: 'main',
823
+ req: new Request('http://localhost'),
824
+ logErrors: true,
825
+ files,
826
+ }),
827
+ env: {},
828
+ files,
829
+ includedComponents: [rootComponent, componentB, componentA, componentC],
830
+ projectId: 'test-project',
831
+ req: {},
832
+ });
833
+ // Component C should render 'expected-value'
834
+ expect(html).toContain('expected-value');
835
+ });
836
+ test('should render ID formula with the expected order', async () => {
837
+ const component = {
838
+ name: 'IdTestComponent',
839
+ nodes: {
840
+ root: {
841
+ type: 'element',
842
+ tag: 'div',
843
+ attrs: {
844
+ 'data-custom-id': {
845
+ type: 'function',
846
+ name: '@toddle/concatenate',
847
+ arguments: [
848
+ {
849
+ name: '0',
850
+ formula: { type: 'value', value: 'custom' },
851
+ type: { type: 'Array \\| String \\| Object' },
852
+ },
853
+ {
854
+ name: '0',
855
+ formula: {
856
+ type: 'function',
857
+ name: '@toddle/Id',
858
+ arguments: [],
859
+ display_name: 'Id',
860
+ },
861
+ type: { type: 'Array \\| String \\| Object' },
862
+ },
863
+ ],
864
+ },
865
+ },
866
+ children: [],
867
+ events: {},
868
+ style: {},
869
+ },
870
+ },
871
+ formulas: {},
872
+ apis: {},
873
+ attributes: {},
874
+ variables: {},
875
+ events: [],
876
+ route: {
877
+ path: [],
878
+ query: {},
879
+ },
880
+ };
881
+ const { html } = await renderPageBody({
882
+ evaluateComponentApis: () => ({}),
883
+ component: component,
884
+ formulaContext: getPageFormulaContext({
885
+ component: component,
886
+ branchName: 'main',
887
+ req: new Request('http://localhost'),
888
+ logErrors: true,
889
+ files: {
890
+ components: { IdTestComponent: component },
891
+ },
892
+ }),
893
+ env: {},
894
+ files: { components: { IdTestComponent: component } },
895
+ includedComponents: [component],
896
+ projectId: 'test-project',
897
+ req: {},
898
+ });
899
+ // Expect the ID formula to render with the correct order (0 in this case since it's the first formula rendered)
900
+ expect(html).toBe(`<div data-custom-id="custom_id_0_" data-id="0" data-node-id="root" class="ftaNwg"></div>`);
901
+ });
902
+ });
903
+ //# sourceMappingURL=components.test.js.map