@lightningjs/blits 0.6.13 → 0.7.1

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.
@@ -0,0 +1,2582 @@
1
+ /*
2
+ * Copyright 2023 Comcast Cable Communications Management, LLC
3
+ * Licensed under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License.
5
+ * You may obtain a copy of the License at
6
+ *
7
+ * http://www.apache.org/licenses/LICENSE-2.0
8
+ *
9
+ * Unless required by applicable law or agreed to in writing, software
10
+ * distributed under the License is distributed on an "AS IS" BASIS,
11
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ * See the License for the specific language governing permissions and
13
+ * limitations under the License.
14
+ *
15
+ * SPDX-License-Identifier: Apache-2.0
16
+ */
17
+
18
+ import test from 'tape'
19
+ import generator from './generator.js'
20
+
21
+ const normalize = (str) => {
22
+ return (
23
+ str
24
+ // remove comments
25
+ .replace(/\s*\/\/.*\n/gi, '')
26
+ // remove whitespaces
27
+ .replace(/[\n\s\t]/gi, '')
28
+ )
29
+ }
30
+
31
+ const scope = {
32
+ components: {},
33
+ }
34
+
35
+ test('Type', (assert) => {
36
+ const expected = 'function'
37
+ const actual = typeof generator
38
+
39
+ assert.equal(actual, expected, 'Generator should be a function')
40
+ assert.end()
41
+ })
42
+
43
+ test('Returns an object with a render function and a context object', (assert) => {
44
+ const result = generator.call(scope)
45
+ const expected = 'object'
46
+ const actual = typeof result
47
+
48
+ assert.equal(actual, expected, 'Generator should return an object')
49
+ assert.ok('render' in result, 'Generated object should have a render key')
50
+ assert.ok('effects' in result, 'Generated object should have an effects key')
51
+ assert.ok('context' in result, 'Generated object should have a context key')
52
+ assert.end()
53
+ })
54
+
55
+ test('The render key is a function', (assert) => {
56
+ const result = generator.call(scope)
57
+ const expected = 'function'
58
+ const actual = typeof result.render
59
+
60
+ assert.equal(actual, expected, 'Render key should return a function')
61
+ assert.end()
62
+ })
63
+
64
+ test('The effects key is an array (of functions)', (assert) => {
65
+ const result = generator.call(scope)
66
+ const actual = Array.isArray(result.effects)
67
+
68
+ assert.ok(actual, 'Update key should return an Array')
69
+ assert.end()
70
+ })
71
+
72
+ test('The contex key is an object', (assert) => {
73
+ const result = generator.call(scope)
74
+ const expected = 'object'
75
+ const actual = typeof result.context
76
+
77
+ assert.equal(actual, expected, 'Context key should return an object')
78
+ assert.end()
79
+ })
80
+
81
+ test('Generate render and effect code for an empty template', (assert) => {
82
+ const expectedRender = `
83
+ function anonymous(parent,component,context) {
84
+ const elms = []
85
+ return elms
86
+ }
87
+ `
88
+
89
+ const actual = generator.call(scope)
90
+
91
+ assert.equal(
92
+ normalize(actual.render.toString()),
93
+ normalize(expectedRender),
94
+ 'Generator should return a render function with the correct code'
95
+ )
96
+ assert.ok(
97
+ Array.isArray(actual.effects) && actual.effects.length === 0,
98
+ 'Generator should return an empty effects array'
99
+ )
100
+ assert.end()
101
+ })
102
+
103
+ test('Generate render and effect code for a template with a single simple element', (assert) => {
104
+ const templateObject = {
105
+ children: [
106
+ {
107
+ [Symbol.for('componentType')]: 'Element',
108
+ },
109
+ ],
110
+ }
111
+
112
+ const expectedRender = `
113
+ function anonymous(parent,component,context) {
114
+ const elms = []
115
+
116
+ if(!elms[0]) {
117
+ elms[0] = this.element({parent: parent || 'root'}, component)
118
+ }
119
+ const elementConfig0 = {}
120
+
121
+ if(!elms[0].nodeId) {
122
+ elms[0].populate(elementConfig0)
123
+ }
124
+ return elms
125
+ }
126
+ `
127
+
128
+ const actual = generator.call(scope, templateObject)
129
+
130
+ assert.equal(
131
+ normalize(actual.render.toString()),
132
+ normalize(expectedRender),
133
+ 'Generator should return a render function with the correct code'
134
+ )
135
+ assert.ok(
136
+ Array.isArray(actual.effects) && actual.effects.length === 0,
137
+ 'Generator should return an empty effects array'
138
+ )
139
+ assert.end()
140
+ })
141
+
142
+ test('Generate code for a template with a simple element and a simple nested element', (assert) => {
143
+ const templateObject = {
144
+ children: [
145
+ {
146
+ [Symbol.for('componentType')]: 'Element',
147
+ children: [
148
+ {
149
+ [Symbol.for('componentType')]: 'Element',
150
+ },
151
+ ],
152
+ },
153
+ ],
154
+ }
155
+
156
+ const expectedRender = `
157
+ function anonymous(parent,component,context) {
158
+ const elms = []
159
+
160
+ if(!elms[0]) {
161
+ elms[0] = this.element({parent: parent || 'root'}, component)
162
+ }
163
+ const elementConfig0 = {}
164
+
165
+ if(!elms[0].nodeId) {
166
+ elms[0].populate(elementConfig0)
167
+ }
168
+
169
+ parent = elms[0]
170
+
171
+ if(!elms[1]) {
172
+ elms[1] = this.element({parent: parent || 'root'}, component)
173
+ }
174
+ const elementConfig1 = {}
175
+
176
+ if(!elms[1].nodeId) {
177
+ elms[1].populate(elementConfig1)
178
+ }
179
+
180
+ return elms
181
+ }
182
+ `
183
+
184
+ const actual = generator.call(scope, templateObject)
185
+
186
+ assert.equal(
187
+ normalize(actual.render.toString()),
188
+ normalize(expectedRender),
189
+ 'Generator should return a render function with the correct code'
190
+ )
191
+ assert.ok(
192
+ Array.isArray(actual.effects) && actual.effects.length === 0,
193
+ 'Generator should return an empty effects array'
194
+ )
195
+ assert.end()
196
+ })
197
+
198
+ test('Generate code for a template with a single element with attributes', (assert) => {
199
+ const templateObject = {
200
+ children: [
201
+ {
202
+ [Symbol.for('componentType')]: 'Element',
203
+ x: '10',
204
+ y: '20',
205
+ },
206
+ ],
207
+ }
208
+
209
+ const expectedRender = `
210
+ function anonymous(parent,component,context) {
211
+ const elms = []
212
+
213
+ if(!elms[0]) {
214
+ elms[0] = this.element({parent: parent || 'root'}, component)
215
+ }
216
+ const elementConfig0 = {}
217
+
218
+ elementConfig0['x'] = 10
219
+ elementConfig0['y'] = 20
220
+
221
+ if(!elms[0].nodeId) {
222
+ elms[0].populate(elementConfig0)
223
+ }
224
+ return elms
225
+ }
226
+ `
227
+
228
+ const actual = generator.call(scope, templateObject)
229
+
230
+ assert.equal(
231
+ normalize(actual.render.toString()),
232
+ normalize(expectedRender),
233
+ 'Generator should return a render function with the correct code'
234
+ )
235
+ assert.ok(
236
+ Array.isArray(actual.effects) && actual.effects.length === 0,
237
+ 'Generator should return an empty effects array'
238
+ )
239
+ assert.end()
240
+ })
241
+
242
+ test('Generate code for a template with a single element with attributes with a boolean like value', (assert) => {
243
+ const templateObject = {
244
+ children: [
245
+ {
246
+ [Symbol.for('componentType')]: 'Element',
247
+ one: 'true',
248
+ two: 'false',
249
+ },
250
+ ],
251
+ }
252
+
253
+ const expectedRender = `
254
+ function anonymous(parent,component,context) {
255
+ const elms = []
256
+
257
+ if(!elms[0]) {
258
+ elms[0] = this.element({parent: parent || 'root'}, component)
259
+ }
260
+ const elementConfig0 = {}
261
+
262
+ elementConfig0['one'] = true
263
+ elementConfig0['two'] = false
264
+
265
+ if(!elms[0].nodeId) {
266
+ elms[0].populate(elementConfig0)
267
+ }
268
+ return elms
269
+ }
270
+ `
271
+
272
+ const actual = generator.call(scope, templateObject)
273
+
274
+ assert.equal(
275
+ normalize(actual.render.toString()),
276
+ normalize(expectedRender),
277
+ 'Generator should return a render function with the correct code'
278
+ )
279
+ assert.ok(
280
+ Array.isArray(actual.effects) && actual.effects.length === 0,
281
+ 'Generator should return an empty effects array'
282
+ )
283
+ assert.end()
284
+ })
285
+
286
+ test('Generate code for a template with attributes and a nested element with attributes', (assert) => {
287
+ const templateObject = {
288
+ children: [
289
+ {
290
+ [Symbol.for('componentType')]: 'Element',
291
+ x: '10',
292
+ y: '20',
293
+ children: [
294
+ {
295
+ [Symbol.for('componentType')]: 'Element',
296
+ w: '100',
297
+ h: '300',
298
+ },
299
+ ],
300
+ },
301
+ ],
302
+ }
303
+
304
+ const expectedRender = `
305
+ function anonymous(parent,component,context) {
306
+ const elms = []
307
+
308
+ if(!elms[0]) {
309
+ elms[0] = this.element({parent: parent || 'root'}, component)
310
+ }
311
+ const elementConfig0 = {}
312
+
313
+ elementConfig0['x'] = 10
314
+ elementConfig0['y'] = 20
315
+
316
+ if(!elms[0].nodeId) {
317
+ elms[0].populate(elementConfig0)
318
+ }
319
+
320
+ parent = elms[0]
321
+
322
+ if(!elms[1]) {
323
+ elms[1] = this.element({parent: parent || 'root'}, component)
324
+ }
325
+ const elementConfig1 = {}
326
+
327
+ elementConfig1['w'] = 100
328
+ elementConfig1['h'] = 300
329
+
330
+ if(!elms[1].nodeId) {
331
+ elms[1].populate(elementConfig1)
332
+ }
333
+
334
+ return elms
335
+ }
336
+ `
337
+
338
+ const actual = generator.call(scope, templateObject)
339
+
340
+ assert.equal(
341
+ normalize(actual.render.toString()),
342
+ normalize(expectedRender),
343
+ 'Generator should return a render function with the correct code'
344
+ )
345
+ assert.ok(
346
+ Array.isArray(actual.effects) && actual.effects.length === 0,
347
+ 'Generator should return an empty effects array'
348
+ )
349
+ assert.end()
350
+ })
351
+
352
+ test('Generate code for a template with attributes and 2 nested elements with attributes', (assert) => {
353
+ const templateObject = {
354
+ children: [
355
+ {
356
+ [Symbol.for('componentType')]: 'Element',
357
+ x: '10',
358
+ y: '20',
359
+ children: [
360
+ {
361
+ [Symbol.for('componentType')]: 'Element',
362
+ w: '100',
363
+ h: '300',
364
+ x: '0',
365
+ },
366
+ {
367
+ [Symbol.for('componentType')]: 'Element',
368
+ w: '100',
369
+ h: '300',
370
+ x: '50',
371
+ },
372
+ ],
373
+ },
374
+ ],
375
+ }
376
+
377
+ const expectedRender = `
378
+ function anonymous(parent,component,context) {
379
+ const elms = []
380
+
381
+ if(!elms[0]) {
382
+ elms[0] = this.element({parent: parent || 'root'}, component)
383
+ }
384
+
385
+ const elementConfig0 = {}
386
+
387
+ elementConfig0['x'] = 10
388
+ elementConfig0['y'] = 20
389
+
390
+ if(!elms[0].nodeId) {
391
+ elms[0].populate(elementConfig0)
392
+ }
393
+
394
+ parent = elms[0]
395
+
396
+ if(!elms[1]) {
397
+ elms[1] = this.element({parent: parent || 'root'}, component)
398
+ }
399
+ const elementConfig1 = {}
400
+
401
+ elementConfig1['w'] = 100
402
+ elementConfig1['h'] = 300
403
+ elementConfig1['x'] = 0
404
+
405
+ if(!elms[1].nodeId) {
406
+ elms[1].populate(elementConfig1)
407
+ }
408
+
409
+ parent = elms[0]
410
+
411
+ if(!elms[2]) {
412
+ elms[2] = this.element({parent: parent || 'root'}, component)
413
+ }
414
+ const elementConfig2 = {}
415
+
416
+ elementConfig2['w'] = 100
417
+ elementConfig2['h'] = 300
418
+ elementConfig2['x'] = 50
419
+
420
+ if(!elms[2].nodeId) {
421
+ elms[2].populate(elementConfig2)
422
+ }
423
+
424
+ return elms
425
+ }
426
+ `
427
+
428
+ const actual = generator.call(scope, templateObject)
429
+
430
+ assert.equal(
431
+ normalize(actual.render.toString()),
432
+ normalize(expectedRender),
433
+ 'Generator should return a render function with the correct code'
434
+ )
435
+ assert.ok(
436
+ Array.isArray(actual.effects) && actual.effects.length === 0,
437
+ 'Generator should return an empty effects array'
438
+ )
439
+ assert.end()
440
+ })
441
+
442
+ test('Generate code for a template with attributes and deep nested elements with attributes', (assert) => {
443
+ const templateObject = {
444
+ children: [
445
+ {
446
+ [Symbol.for('componentType')]: 'Element',
447
+ x: '10',
448
+ y: '20',
449
+ children: [
450
+ {
451
+ [Symbol.for('componentType')]: 'Element',
452
+ w: '100',
453
+ h: '300',
454
+ x: '0',
455
+ },
456
+ {
457
+ [Symbol.for('componentType')]: 'Element',
458
+ w: '100',
459
+ h: '300',
460
+ x: '50',
461
+ children: [
462
+ {
463
+ [Symbol.for('componentType')]: 'Element',
464
+ label: 'Hello',
465
+ },
466
+ {
467
+ [Symbol.for('componentType')]: 'Element',
468
+ label: 'World',
469
+ },
470
+ ],
471
+ },
472
+ ],
473
+ },
474
+ ],
475
+ }
476
+
477
+ const expectedRender = `
478
+ function anonymous(parent,component,context) {
479
+ const elms = []
480
+
481
+ if(!elms[0]) {
482
+ elms[0] = this.element({parent: parent || 'root'}, component)
483
+ }
484
+ const elementConfig0 = {}
485
+
486
+ elementConfig0['x'] = 10
487
+ elementConfig0['y'] = 20
488
+
489
+ if(!elms[0].nodeId) {
490
+ elms[0].populate(elementConfig0)
491
+ }
492
+
493
+ parent = elms[0]
494
+
495
+ if(!elms[1]) {
496
+ elms[1] = this.element({parent: parent || 'root'}, component)
497
+ }
498
+
499
+ const elementConfig1 = {}
500
+
501
+ elementConfig1['w'] = 100
502
+ elementConfig1['h'] = 300
503
+ elementConfig1['x'] = 0
504
+
505
+ if(!elms[1].nodeId) {
506
+ elms[1].populate(elementConfig1)
507
+ }
508
+
509
+ parent = elms[0]
510
+
511
+ if(!elms[2]) {
512
+ elms[2] = this.element({parent: parent || 'root'}, component)
513
+ }
514
+
515
+ const elementConfig2 = {}
516
+
517
+ elementConfig2['w'] = 100
518
+ elementConfig2['h'] = 300
519
+ elementConfig2['x'] = 50
520
+
521
+ if(!elms[2].nodeId) {
522
+ elms[2].populate(elementConfig2)
523
+ }
524
+
525
+ parent = elms[2]
526
+
527
+ if(!elms[3]) {
528
+ elms[3] = this.element({parent: parent || 'root'}, component)
529
+ }
530
+ const elementConfig3 = {}
531
+
532
+ elementConfig3['label'] = "Hello"
533
+
534
+ if(!elms[3].nodeId) {
535
+ elms[3].populate(elementConfig3)
536
+ }
537
+
538
+ parent = elms[2]
539
+
540
+ if(!elms[4]) {
541
+ elms[4] = this.element({parent: parent || 'root'}, component)
542
+ }
543
+ const elementConfig4 = {}
544
+
545
+ elementConfig4['label'] = "World"
546
+
547
+ if(!elms[4].nodeId) {
548
+ elms[4].populate(elementConfig4)
549
+ }
550
+
551
+ return elms
552
+ }
553
+ `
554
+
555
+ const actual = generator.call(scope, templateObject)
556
+
557
+ assert.equal(
558
+ normalize(actual.render.toString()),
559
+ normalize(expectedRender),
560
+ 'Generator should return a render function with the correct code'
561
+ )
562
+ assert.ok(
563
+ Array.isArray(actual.effects) && actual.effects.length === 0,
564
+ 'Generator should return an empty effects array'
565
+ )
566
+ assert.end()
567
+ })
568
+
569
+ test('Generate code for a template with simple dynamic attributes', (assert) => {
570
+ const templateObject = {
571
+ children: [
572
+ {
573
+ [Symbol.for('componentType')]: 'Element',
574
+ x: '10',
575
+ y: '20',
576
+ ':w': '$foo',
577
+ ':h': '$test',
578
+ test: 'ok',
579
+ },
580
+ ],
581
+ }
582
+
583
+ const expectedRender = `
584
+ function anonymous(parent,component,context) {
585
+ const elms = []
586
+
587
+ if(!elms[0]) {
588
+ elms[0] = this.element({parent: parent || 'root'}, component)
589
+ }
590
+ const elementConfig0 = {}
591
+
592
+ elementConfig0['x'] = 10
593
+ elementConfig0['y'] = 20
594
+ elementConfig0['test'] = "ok"
595
+
596
+ if(!elms[0].nodeId) {
597
+ elms[0].populate(elementConfig0)
598
+ }
599
+
600
+ return elms
601
+ }
602
+ `
603
+
604
+ const expectedEffect1 = `
605
+ function anonymous(component,elms,context) {
606
+ elms[0].set('w', component.foo)
607
+ }
608
+ `
609
+
610
+ const expectedEffect2 = `
611
+ function anonymous(component,elms,context) {
612
+ elms[0].set('h', component.test)
613
+ }
614
+ `
615
+
616
+ const actual = generator.call(scope, templateObject)
617
+
618
+ assert.equal(
619
+ normalize(actual.render.toString()),
620
+ normalize(expectedRender),
621
+ 'Generator should return a render function with the correct code'
622
+ )
623
+ assert.ok(
624
+ Array.isArray(actual.effects) && actual.effects.length === 2,
625
+ 'Generator should return an effects array with 2 items'
626
+ )
627
+ assert.equal(
628
+ normalize(actual.effects[0].toString()),
629
+ normalize(expectedEffect1),
630
+ 'Generator should return first render function with the correct code'
631
+ )
632
+ assert.equal(
633
+ normalize(actual.effects[1].toString()),
634
+ normalize(expectedEffect2),
635
+ 'Generator should return second render function with the correct code'
636
+ )
637
+
638
+ assert.end()
639
+ })
640
+
641
+ test('Generate code for a template with an attribute with a dash', (assert) => {
642
+ const templateObject = {
643
+ children: [
644
+ {
645
+ [Symbol.for('componentType')]: 'Element',
646
+ 'my-Attribute': 'does it work?',
647
+ x: '10',
648
+ y: '20',
649
+ },
650
+ ],
651
+ }
652
+
653
+ const expectedRender = `
654
+ function anonymous(parent,component,context) {
655
+ const elms = []
656
+
657
+ if(!elms[0]) {
658
+ elms[0] = this.element({parent: parent || 'root'}, component)
659
+ }
660
+ const elementConfig0 = {}
661
+
662
+ elementConfig0['my-Attribute'] = "does it work?"
663
+ elementConfig0['x'] = 10
664
+ elementConfig0['y'] = 20
665
+
666
+ if(!elms[0].nodeId) {
667
+ elms[0].populate(elementConfig0)
668
+ }
669
+
670
+ return elms
671
+ }
672
+ `
673
+
674
+ const actual = generator.call(scope, templateObject)
675
+
676
+ assert.equal(
677
+ normalize(actual.render.toString()),
678
+ normalize(expectedRender),
679
+ 'Generator should return a render function with the correct code'
680
+ )
681
+ assert.ok(
682
+ Array.isArray(actual.effects) && actual.effects.length === 0,
683
+ 'Generator should return an empty effects array'
684
+ )
685
+ assert.end()
686
+ })
687
+
688
+ test('Generate code for a template with dynamic attributes with code to be evaluated', (assert) => {
689
+ const templateObject = {
690
+ children: [
691
+ {
692
+ [Symbol.for('componentType')]: 'Element',
693
+ ':attribute1': '$foo * 2',
694
+ ':attribute2': "$ok ? 'Yes' : 'No'",
695
+ ':attribute3': "$text.split('').reverse().join('')",
696
+ // ':attribute4': '$bar.foo * $blah.hello' => todo
697
+ },
698
+ ],
699
+ }
700
+
701
+ const expectedRender = `
702
+ function anonymous(parent,component,context) {
703
+ const elms = []
704
+
705
+ if(!elms[0]) {
706
+ elms[0] = this.element({parent: parent || 'root'}, component)
707
+ }
708
+ const elementConfig0 = {}
709
+
710
+ if(!elms[0].nodeId) {
711
+ elms[0].populate(elementConfig0)
712
+ }
713
+
714
+ return elms
715
+ }
716
+ `
717
+
718
+ const expectedEffect1 = `
719
+ function anonymous(component,elms,context) {
720
+ elms[0].set('attribute1', component.foo * 2)
721
+ }
722
+ `
723
+
724
+ const expectedEffect2 = `
725
+ function anonymous(component,elms,context) {
726
+ elms[0].set('attribute2', component.ok ? 'Yes' : 'No')
727
+ }
728
+ `
729
+
730
+ const expectedEffect3 = `
731
+ function anonymous(component,elms,context) {
732
+ elms[0].set('attribute3', component.text.split('').reverse().join(''))
733
+ }
734
+ `
735
+
736
+ const actual = generator.call(scope, templateObject)
737
+
738
+ assert.equal(
739
+ normalize(actual.render.toString()),
740
+ normalize(expectedRender),
741
+ 'Generator should return a render function with the correct code'
742
+ )
743
+ assert.ok(
744
+ Array.isArray(actual.effects) && actual.effects.length === 3,
745
+ 'Generator should return an effects array with 3 items'
746
+ )
747
+ assert.equal(
748
+ normalize(actual.effects[0].toString()),
749
+ normalize(expectedEffect1),
750
+ 'Generator should return first effect function with the correct code'
751
+ )
752
+ assert.equal(
753
+ normalize(actual.effects[1].toString()),
754
+ normalize(expectedEffect2),
755
+ 'Generator should return second effect function with the correct code'
756
+ )
757
+ assert.equal(
758
+ normalize(actual.effects[2].toString()),
759
+ normalize(expectedEffect3),
760
+ 'Generator should return third effect function with the correct code'
761
+ )
762
+
763
+ assert.end()
764
+ })
765
+
766
+ test('Generate code for a template with @-listeners', (assert) => {
767
+ const templateObject = {
768
+ children: [
769
+ {
770
+ [Symbol.for('componentType')]: 'Element',
771
+ src: 'myImage.png',
772
+ '@loaded': '$loadedCallback',
773
+ '@error': '$errorCallback',
774
+ },
775
+ ],
776
+ }
777
+
778
+ const expectedRender = `
779
+ function anonymous(parent,component,context) {
780
+ const elms = []
781
+
782
+ if(!elms[0]) {
783
+ elms[0] = this.element({parent: parent || 'root'}, component)
784
+ }
785
+ const elementConfig0 = {}
786
+
787
+ elementConfig0['src'] = "myImage.png"
788
+ elementConfig0['@loaded'] = component['loadedCallback'] && component['loadedCallback'].bind(component)
789
+ elementConfig0['@error'] = component['errorCallback'] && component['errorCallback'].bind(component)
790
+
791
+ if(!elms[0].nodeId) {
792
+ elms[0].populate(elementConfig0)
793
+ }
794
+ return elms
795
+ }
796
+ `
797
+
798
+ const actual = generator.call(scope, templateObject)
799
+
800
+ assert.equal(
801
+ normalize(actual.render.toString()),
802
+ normalize(expectedRender),
803
+ 'Generator should return a render function with the correct code'
804
+ )
805
+ assert.ok(
806
+ Array.isArray(actual.effects) && actual.effects.length === 0,
807
+ 'Generator should return an empty effects array'
808
+ )
809
+ assert.end()
810
+ })
811
+
812
+ test('Generate code for a template with custom components', (assert) => {
813
+ const templateObject = {
814
+ children: [
815
+ {
816
+ [Symbol.for('componentType')]: 'Element',
817
+ children: [
818
+ {
819
+ [Symbol.for('componentType')]: 'Poster',
820
+ },
821
+ {
822
+ [Symbol.for('componentType')]: 'Poster',
823
+ },
824
+ ],
825
+ },
826
+ ],
827
+ }
828
+
829
+ const scope = {
830
+ components: {
831
+ Poster: () => {},
832
+ },
833
+ }
834
+
835
+ const expectedRender = `
836
+ function anonymous(parent,component,context) {
837
+ const elms = []
838
+
839
+ if(!elms[0]) {
840
+ elms[0] = this.element({parent: parent || 'root'}, component)
841
+ }
842
+ const elementConfig0 = {}
843
+
844
+ if(!elms[0].nodeId) {
845
+ elms[0].populate(elementConfig0)
846
+ }
847
+
848
+ const cmp1 = (context.components && context.components['Poster']) ||
849
+ component[Symbol.for('components')]['Poster']
850
+
851
+ parent = elms[0]
852
+
853
+ if(!elms[1]) {
854
+ elms[1] = this.element({parent: parent || 'root'}, component)
855
+ }
856
+
857
+ const elementConfig1 = {}
858
+ if(typeof cmp1 !== 'undefined') {
859
+ for(let key in cmp1.config.props) {
860
+ delete elementConfig1[cmp1.config.props[key]]
861
+ }
862
+ }
863
+
864
+ if(!elms[1].nodeId) {
865
+ elms[1].populate(elementConfig1)
866
+ }
867
+
868
+ parent = elms[1]
869
+ const props2 = {}
870
+ if(!elms[2]) {
871
+ const componentType = props2['is'] || 'Poster'
872
+ elms[2] = (context.components && context.components[componentType] || component[Symbol.for('components')][componentType] || (() => { console.error('component Poster not found')})).call(null, {props: props2}, elms[1], component)
873
+ if (elms[2][Symbol.for('slots')][0]) {
874
+ parent = elms[2][Symbol.for('slots')][0]
875
+ component = elms[2]
876
+ } else {
877
+ parent = elms[2][Symbol.for('children')][0]
878
+ }
879
+ }
880
+
881
+ const cmp3 = (context.components && context.components['Poster']) ||
882
+ component[Symbol.for('components')]['Poster']
883
+
884
+ parent = elms[0]
885
+
886
+ if(!elms[3]) {
887
+ elms[3] = this.element({parent: parent || 'root'}, component)
888
+ }
889
+
890
+ const elementConfig3 = {}
891
+ if(typeof cmp3 !== 'undefined') {
892
+ for(let key in cmp3.config.props) {
893
+ delete elementConfig3[cmp3.config.props[key]]
894
+ }
895
+ }
896
+
897
+ if(!elms[3].nodeId) {
898
+ elms[3].populate(elementConfig3)
899
+ }
900
+
901
+ parent = elms[3]
902
+ const props4 = {}
903
+ if(!elms[4]) {
904
+ const componentType = props4['is'] || 'Poster'
905
+ elms[4] = (context.components && context.components[componentType] || component[Symbol.for('components')][componentType] || (() => { console.error('component Poster not found')})).call(null, {props: props4}, elms[3], component)
906
+ if (elms[4][Symbol.for('slots')][0]) {
907
+ parent = elms[4][Symbol.for('slots')][0]
908
+ component = elms[4]
909
+ } else {
910
+ parent = elms[4][Symbol.for('children')][0]
911
+ }
912
+ }
913
+
914
+ return elms
915
+ }
916
+ `
917
+
918
+ const actual = generator.call(scope, templateObject)
919
+
920
+ assert.equal(
921
+ normalize(actual.render.toString()),
922
+ normalize(expectedRender),
923
+ 'Generator should return a render function with the correct code'
924
+ )
925
+ assert.ok(
926
+ Array.isArray(actual.effects) && actual.effects.length === 0,
927
+ 'Generator should return an empty effects array'
928
+ )
929
+
930
+ assert.end()
931
+ })
932
+
933
+ test('Generate code for a template with an unregistered custom component', (assert) => {
934
+ const templateObject = {
935
+ children: [
936
+ {
937
+ [Symbol.for('componentType')]: 'Element',
938
+ children: [
939
+ {
940
+ [Symbol.for('componentType')]: 'Poster',
941
+ },
942
+ {
943
+ [Symbol.for('componentType')]: 'Poster2',
944
+ },
945
+ ],
946
+ },
947
+ ],
948
+ }
949
+
950
+ const scope = {
951
+ components: {
952
+ Poster: () => {},
953
+ },
954
+ }
955
+
956
+ const expectedRender = `
957
+ function anonymous(parent,component,context) {
958
+ const elms = []
959
+
960
+ if(!elms[0]) {
961
+ elms[0] = this.element({parent: parent || 'root'}, component)
962
+ }
963
+ const elementConfig0 = {}
964
+
965
+ if(!elms[0].nodeId) {
966
+ elms[0].populate(elementConfig0)
967
+ }
968
+
969
+ const cmp1 = (context.components && context.components['Poster']) ||
970
+ component[Symbol.for('components')]['Poster']
971
+
972
+ parent = elms[0]
973
+
974
+ if(!elms[1]) {
975
+ elms[1] = this.element({parent: parent || 'root'}, component)
976
+ }
977
+
978
+ const elementConfig1 = {}
979
+ if(typeof cmp1 !== 'undefined') {
980
+ for(let key in cmp1.config.props) {
981
+ delete elementConfig1[cmp1.config.props[key]]
982
+ }
983
+ }
984
+
985
+ if(!elms[1].nodeId) {
986
+ elms[1].populate(elementConfig1)
987
+ }
988
+
989
+ parent = elms[1]
990
+ const props2 = {}
991
+ if(!elms[2]) {
992
+ const componentType = props2['is'] || 'Poster'
993
+ elms[2] = (context.components && context.components[componentType] || component[Symbol.for('components')][componentType] || (() => { console.error('component Poster not found')})).call(null, {props: props2}, elms[1], component)
994
+ if (elms[2][Symbol.for('slots')][0]) {
995
+ parent = elms[2][Symbol.for('slots')][0]
996
+ component = elms[2]
997
+ } else {
998
+ parent = elms[2][Symbol.for('children')][0]
999
+ }
1000
+ }
1001
+
1002
+ const cmp3 = (context.components && context.components['Poster2']) ||
1003
+ component[Symbol.for('components')]['Poster2']
1004
+
1005
+ parent = elms[0]
1006
+
1007
+ if(!elms[3]) {
1008
+ elms[3] = this.element({parent: parent || 'root'}, component)
1009
+ }
1010
+
1011
+ const elementConfig3 = {}
1012
+ if(typeof cmp3 !== 'undefined') {
1013
+ for(let key in cmp3.config.props) {
1014
+ delete elementConfig3[cmp3.config.props[key]]
1015
+ }
1016
+ }
1017
+
1018
+ if(!elms[3].nodeId) {
1019
+ elms[3].populate(elementConfig3)
1020
+ }
1021
+
1022
+ parent = elms[3]
1023
+ const props4 = {}
1024
+ if(!elms[4]) {
1025
+ const componentType = props4['is'] || 'Poster2'
1026
+ elms[4] = (context.components && context.components[componentType] || component[Symbol.for('components')][componentType] || (() => { console.error('component Poster2 not found')})).call(null, {props: props4}, elms[3], component)
1027
+ if (elms[4][Symbol.for('slots')][0]) {
1028
+ parent = elms[4][Symbol.for('slots')][0]
1029
+ component = elms[4]
1030
+ } else {
1031
+ parent = elms[4][Symbol.for('children')][0]
1032
+ }
1033
+ }
1034
+
1035
+ return elms
1036
+ }
1037
+ `
1038
+
1039
+ const actual = generator.call(scope, templateObject)
1040
+
1041
+ assert.equal(
1042
+ normalize(actual.render.toString()),
1043
+ normalize(expectedRender),
1044
+ 'Generator should return a render function with the correct code'
1045
+ )
1046
+ assert.ok(
1047
+ Array.isArray(actual.effects) && actual.effects.length === 0,
1048
+ 'Generator should return an empty effects array'
1049
+ )
1050
+
1051
+ assert.end()
1052
+ })
1053
+
1054
+ test('Generate code for a template with custom components with arguments', (assert) => {
1055
+ const templateObject = {
1056
+ children: [
1057
+ {
1058
+ [Symbol.for('componentType')]: 'Element',
1059
+ children: [
1060
+ {
1061
+ [Symbol.for('componentType')]: 'Poster',
1062
+ x: '10',
1063
+ },
1064
+ {
1065
+ [Symbol.for('componentType')]: 'Poster',
1066
+ x: '100',
1067
+ img: '$img',
1068
+ },
1069
+ ],
1070
+ },
1071
+ ],
1072
+ }
1073
+
1074
+ const scope = {
1075
+ components: {
1076
+ Poster: () => {},
1077
+ },
1078
+ }
1079
+
1080
+ const expectedRender = `
1081
+ function anonymous(parent,component,context) {
1082
+ const elms = []
1083
+
1084
+ if(!elms[0]) {
1085
+ elms[0] = this.element({parent: parent || 'root'}, component)
1086
+ }
1087
+ const elementConfig0 = {}
1088
+
1089
+ if(!elms[0].nodeId) {
1090
+ elms[0].populate(elementConfig0)
1091
+ }
1092
+
1093
+ const cmp1 = (context.components && context.components['Poster']) ||
1094
+ component[Symbol.for('components')]['Poster']
1095
+
1096
+ parent = elms[0]
1097
+
1098
+ if(!elms[1]) {
1099
+ elms[1] = this.element({parent: parent || 'root'}, component)
1100
+ }
1101
+
1102
+ const elementConfig1 = {}
1103
+
1104
+ elementConfig1['x'] = 10
1105
+
1106
+ if(typeof cmp1 !== 'undefined') {
1107
+ for(let key in cmp1.config.props) {
1108
+ delete elementConfig1[cmp1.config.props[key]]
1109
+ }
1110
+ }
1111
+
1112
+ if(!elms[1].nodeId) {
1113
+ elms[1].populate(elementConfig1)
1114
+ }
1115
+
1116
+ parent = elms[1]
1117
+ const props2 = {}
1118
+
1119
+ props2['x'] = 10
1120
+
1121
+ if(!elms[2]) {
1122
+ const componentType = props2['is'] || 'Poster'
1123
+ elms[2] = (context.components && context.components[componentType] || component[Symbol.for('components')][componentType] || (() => { console.error('component Poster not found')})).call(null, {props: props2}, elms[1], component)
1124
+ if (elms[2][Symbol.for('slots')][0]) {
1125
+ parent = elms[2][Symbol.for('slots')][0]
1126
+ component = elms[2]
1127
+ } else {
1128
+ parent = elms[2][Symbol.for('children')][0]
1129
+ }
1130
+ }
1131
+
1132
+ const cmp3 = (context.components && context.components['Poster']) ||
1133
+ component[Symbol.for('components')]['Poster']
1134
+
1135
+ parent = elms[0]
1136
+
1137
+ if(!elms[3]) {
1138
+ elms[3] = this.element({parent: parent || 'root'}, component)
1139
+ }
1140
+
1141
+ const elementConfig3 = {}
1142
+
1143
+ elementConfig3['x'] = 100
1144
+ elementConfig3['img'] = component.img
1145
+
1146
+ if(typeof cmp3 !== 'undefined') {
1147
+ for(let key in cmp3.config.props) {
1148
+ delete elementConfig3[cmp3.config.props[key]]
1149
+ }
1150
+ }
1151
+
1152
+ if(!elms[3].nodeId) {
1153
+ elms[3].populate(elementConfig3)
1154
+ }
1155
+
1156
+ parent = elms[3]
1157
+ const props4 = {}
1158
+
1159
+ props4['x'] = 100
1160
+ props4['img'] = component.img
1161
+
1162
+ if(!elms[4]) {
1163
+ const componentType = props4['is'] || 'Poster'
1164
+ elms[4] = (context.components && context.components[componentType] || component[Symbol.for('components')][componentType] || (() => { console.error('component Poster not found')})).call(null, {props: props4}, elms[3], component)
1165
+ if (elms[4][Symbol.for('slots')][0]) {
1166
+ parent = elms[4][Symbol.for('slots')][0]
1167
+ component = elms[4]
1168
+ } else {
1169
+ parent = elms[4][Symbol.for('children')][0]
1170
+ }
1171
+ }
1172
+
1173
+ return elms
1174
+ }
1175
+ `
1176
+
1177
+ const actual = generator.call(scope, templateObject)
1178
+
1179
+ assert.equal(
1180
+ normalize(actual.render.toString()),
1181
+ normalize(expectedRender),
1182
+ 'Generator should return a render function with the correct code'
1183
+ )
1184
+ assert.ok(
1185
+ Array.isArray(actual.effects) && actual.effects.length === 0,
1186
+ 'Generator should return an empty effects array'
1187
+ )
1188
+
1189
+ assert.end()
1190
+ })
1191
+
1192
+ test('Generate code for a template with custom components with reactive props', (assert) => {
1193
+ const templateObject = {
1194
+ children: [
1195
+ {
1196
+ [Symbol.for('componentType')]: 'Element',
1197
+ children: [
1198
+ {
1199
+ [Symbol.for('componentType')]: 'Poster',
1200
+ x: '10',
1201
+ ':img': '$image',
1202
+ },
1203
+ {
1204
+ [Symbol.for('componentType')]: 'Poster',
1205
+ x: '100',
1206
+ ':img': '$image',
1207
+ },
1208
+ ],
1209
+ },
1210
+ ],
1211
+ }
1212
+
1213
+ const scope = {
1214
+ components: {
1215
+ Poster: () => {},
1216
+ },
1217
+ }
1218
+
1219
+ const expectedRender = `
1220
+ function anonymous(parent,component,context) {
1221
+ const elms = []
1222
+
1223
+ if(!elms[0]) {
1224
+ elms[0] = this.element({parent: parent || 'root'}, component)
1225
+ }
1226
+ const elementConfig0 = {}
1227
+
1228
+ if(!elms[0].nodeId) {
1229
+ elms[0].populate(elementConfig0)
1230
+ }
1231
+
1232
+ const cmp1 = (context.components && context.components['Poster']) ||
1233
+ component[Symbol.for('components')]['Poster']
1234
+
1235
+ parent = elms[0]
1236
+
1237
+ if(!elms[1]) {
1238
+ elms[1] = this.element({parent: parent || 'root'}, component)
1239
+ }
1240
+
1241
+ const elementConfig1 = {}
1242
+
1243
+ elementConfig1['x'] = 10
1244
+
1245
+ if(typeof cmp1 !== 'undefined') {
1246
+ for(let key in cmp1.config.props) {
1247
+ delete elementConfig1[cmp1.config.props[key]]
1248
+ }
1249
+ }
1250
+
1251
+ if(!elms[1].nodeId) {
1252
+ elms[1].populate(elementConfig1)
1253
+ }
1254
+
1255
+ parent = elms[1]
1256
+ const props2 = {}
1257
+
1258
+ props2['x'] = 10
1259
+ props2['img'] = component.image
1260
+
1261
+ if(!elms[2]) {
1262
+ const componentType = props2['is'] || 'Poster'
1263
+ elms[2] = (context.components && context.components[componentType] || component[Symbol.for('components')][componentType] || (() => { console.error('component Poster not found')})).call(null, {props: props2}, elms[1], component)
1264
+ if (elms[2][Symbol.for('slots')][0]) {
1265
+ parent = elms[2][Symbol.for('slots')][0]
1266
+ component = elms[2]
1267
+ } else {
1268
+ parent = elms[2][Symbol.for('children')][0]
1269
+ }
1270
+ }
1271
+
1272
+ const cmp3 = (context.components && context.components['Poster']) ||
1273
+ component[Symbol.for('components')]['Poster']
1274
+
1275
+ parent = elms[0]
1276
+
1277
+ if(!elms[3]) {
1278
+ elms[3] = this.element({parent: parent || 'root'}, component)
1279
+ }
1280
+
1281
+ const elementConfig3 = {}
1282
+
1283
+ elementConfig3['x'] = 100
1284
+
1285
+ if(typeof cmp3 !== 'undefined') {
1286
+ for(let key in cmp3.config.props) {
1287
+ delete elementConfig3[cmp3.config.props[key]]
1288
+ }
1289
+ }
1290
+
1291
+ if(!elms[3].nodeId) {
1292
+ elms[3].populate(elementConfig3)
1293
+ }
1294
+
1295
+ parent = elms[3]
1296
+ const props4 = {}
1297
+
1298
+ props4['x'] = 100
1299
+ props4['img'] = component.image
1300
+
1301
+ if(!elms[4]) {
1302
+ const componentType = props4['is'] || 'Poster'
1303
+ elms[4] = (context.components && context.components[componentType] || component[Symbol.for('components')][componentType] || (() => { console.error('component Poster not found')})).call(null, {props: props4}, elms[3], component)
1304
+ if (elms[4][Symbol.for('slots')][0]) {
1305
+ parent = elms[4][Symbol.for('slots')][0]
1306
+ component = elms[4]
1307
+ } else {
1308
+ parent = elms[4][Symbol.for('children')][0]
1309
+ }
1310
+ }
1311
+
1312
+ return elms
1313
+ }
1314
+ `
1315
+
1316
+ const expectedEffect1 = `
1317
+ function anonymous(component,elms,context) {
1318
+ elms[1].set('img', component.image)
1319
+ }
1320
+ `
1321
+
1322
+ const expectedEffect2 = `
1323
+ function anonymous(component,elms,context) {
1324
+ elms[2][Symbol.for('props')]['img'] = component.image
1325
+ }
1326
+ `
1327
+
1328
+ const expectedEffect3 = `
1329
+ function anonymous(component,elms,context) {
1330
+ elms[3].set('img', component.image)
1331
+ }
1332
+ `
1333
+
1334
+ const expectedEffect4 = `
1335
+ function anonymous(component,elms,context) {
1336
+ elms[4][Symbol.for('props')]['img'] = component.image
1337
+ }
1338
+ `
1339
+
1340
+ const actual = generator.call(scope, templateObject)
1341
+
1342
+ assert.equal(
1343
+ normalize(actual.render.toString()),
1344
+ normalize(expectedRender),
1345
+ 'Generator should return a render function with the correct code'
1346
+ )
1347
+
1348
+ assert.ok(
1349
+ Array.isArray(actual.effects) && actual.effects.length === 4,
1350
+ 'Generator should return an effects array with 4 functions'
1351
+ )
1352
+
1353
+ assert.equal(
1354
+ normalize(actual.effects[0].toString()),
1355
+ normalize(expectedEffect1),
1356
+ 'Generator should return an effect function that sets the value on the node'
1357
+ )
1358
+
1359
+ assert.equal(
1360
+ normalize(actual.effects[1].toString()),
1361
+ normalize(expectedEffect2),
1362
+ 'Generator should return an effect function that updates a prop on the first custom component'
1363
+ )
1364
+
1365
+ assert.equal(
1366
+ normalize(actual.effects[2].toString()),
1367
+ normalize(expectedEffect3),
1368
+ 'Generator should return an effect function that sets the value on the node'
1369
+ )
1370
+
1371
+ assert.equal(
1372
+ normalize(actual.effects[3].toString()),
1373
+ normalize(expectedEffect4),
1374
+ 'Generator should return an effect function that updates a prop on the second custom component'
1375
+ )
1376
+
1377
+ assert.end()
1378
+ })
1379
+
1380
+ test('Generate code for a template with a transition attributes', (assert) => {
1381
+ const templateObject = {
1382
+ children: [
1383
+ {
1384
+ [Symbol.for('componentType')]: 'Element',
1385
+ x: '{transition: $myX}',
1386
+ y: '{transition: {v: $myY, d: 600, p: 100}}',
1387
+ },
1388
+ ],
1389
+ }
1390
+
1391
+ const expectedRender = `
1392
+ function anonymous(parent,component,context) {
1393
+ const elms = []
1394
+
1395
+ if(!elms[0]) {
1396
+ elms[0] = this.element({parent: parent || 'root'}, component)
1397
+ }
1398
+ const elementConfig0 = {}
1399
+
1400
+ elementConfig0['x'] = "{transition: $myX}"
1401
+ elementConfig0['y'] = "{transition: {v: $myY, d: 600, p: 100}}"
1402
+
1403
+ if(!elms[0].nodeId) {
1404
+ elms[0].populate(elementConfig0)
1405
+ }
1406
+
1407
+ return elms
1408
+ }
1409
+ `
1410
+
1411
+ const actual = generator.call(scope, templateObject)
1412
+
1413
+ assert.equal(
1414
+ normalize(actual.render.toString()),
1415
+ normalize(expectedRender),
1416
+ 'Generator should return a render function with the correct code'
1417
+ )
1418
+ assert.ok(
1419
+ Array.isArray(actual.effects) && actual.effects.length === 0,
1420
+ 'Generator should return an empty effects array'
1421
+ )
1422
+ assert.end()
1423
+ })
1424
+
1425
+ test('Generate code for a template with slot content', (assert) => {
1426
+ const scope = {
1427
+ components: {
1428
+ Page: () => {},
1429
+ },
1430
+ }
1431
+
1432
+ const templateObject = {
1433
+ children: [
1434
+ {
1435
+ [Symbol.for('componentType')]: 'Element',
1436
+ children: [
1437
+ {
1438
+ [Symbol.for('componentType')]: 'Page',
1439
+ w: '1920',
1440
+ h: '1080',
1441
+ children: [
1442
+ {
1443
+ [Symbol.for('componentType')]: 'Element',
1444
+ x: '100',
1445
+ y: '$y',
1446
+ ':rotation': '$rotate',
1447
+ },
1448
+ ],
1449
+ },
1450
+ ],
1451
+ },
1452
+ ],
1453
+ }
1454
+
1455
+ const expectedRender = `
1456
+ function anonymous(parent,component,context) {
1457
+ const elms = []
1458
+
1459
+ if(!elms[0]) {
1460
+ elms[0] = this.element({parent: parent || 'root'}, component)
1461
+ }
1462
+ const elementConfig0 = {}
1463
+
1464
+ if(!elms[0].nodeId) {
1465
+ elms[0].populate(elementConfig0)
1466
+ }
1467
+
1468
+ const cmp1 = (context.components && context.components['Page']) ||
1469
+ component[Symbol.for('components')]['Page']
1470
+
1471
+ parent = elms[0]
1472
+
1473
+ if(!elms[1]) {
1474
+ elms[1] = this.element({parent: parent || 'root'}, component)
1475
+ }
1476
+ const elementConfig1 = {}
1477
+ elementConfig1['w'] = 1920
1478
+ elementConfig1['h'] = 1080
1479
+
1480
+ if(typeof cmp1 !== 'undefined') {
1481
+ for(let key in cmp1.config.props) {
1482
+ delete elementConfig1[cmp1.config.props[key]]
1483
+ }
1484
+ }
1485
+
1486
+ if(!elms[1].nodeId) {
1487
+ elms[1].populate(elementConfig1)
1488
+ }
1489
+
1490
+ parent = elms[1]
1491
+ const props2 = {}
1492
+ props2['w'] = 1920
1493
+ props2['h'] = 1080
1494
+
1495
+
1496
+ if(!elms[2]) {
1497
+ const componentType = props2['is'] || 'Page'
1498
+ elms[2] = (context.components && context.components[componentType] || component[Symbol.for('components')][componentType] || (() => { console.error('component Page not found')})).call(null, {props: props2}, elms[1], component)
1499
+ if (elms[2][Symbol.for('slots')][0]) {
1500
+ parent = elms[2][Symbol.for('slots')][0]
1501
+ component = elms[2]
1502
+ } else {
1503
+ parent = elms[2][Symbol.for('children')][0]
1504
+ }
1505
+ }
1506
+
1507
+ if(!elms[3]) {
1508
+ elms[3] = this.element({parent: parent || 'root'}, component)
1509
+ }
1510
+
1511
+ const elementConfig3 = {}
1512
+
1513
+ if(!elms[3].nodeId) {
1514
+ elms[3].populate(elementConfig3)
1515
+ }
1516
+ parent = elms[3]
1517
+
1518
+
1519
+ if(!elms[4]) {
1520
+ elms[4] = this.element({parent: parent || 'root'}, component)
1521
+ }
1522
+ const elementConfig4 = {}
1523
+ elementConfig4['x'] = 100
1524
+ elementConfig4['y'] = component.y
1525
+ if(!elms[4].nodeId) {
1526
+ elms[4].populate(elementConfig4)
1527
+ }
1528
+
1529
+ return elms
1530
+ }
1531
+ `
1532
+
1533
+ const expectedEffect1 = `
1534
+ function anonymous(component,elms,context) {
1535
+ elms[4].set('rotation', component.rotate)
1536
+ }
1537
+ `
1538
+
1539
+ const actual = generator.call(scope, templateObject)
1540
+
1541
+ assert.equal(
1542
+ normalize(actual.render.toString()),
1543
+ normalize(expectedRender),
1544
+ 'Generator should return a render function with the correct code'
1545
+ )
1546
+
1547
+ assert.ok(
1548
+ Array.isArray(actual.effects) && actual.effects.length === 1,
1549
+ 'Generator should return an effects array with 1 item'
1550
+ )
1551
+
1552
+ assert.equal(
1553
+ normalize(actual.effects[0].toString()),
1554
+ normalize(expectedEffect1),
1555
+ 'Generator should return an effect function for the reactive rotation attribute'
1556
+ )
1557
+
1558
+ assert.end()
1559
+ })
1560
+
1561
+ test('Generate code for a template with slot content, using a named slot', (assert) => {
1562
+ const scope = {
1563
+ components: {
1564
+ Page: () => {},
1565
+ },
1566
+ }
1567
+
1568
+ const templateObject = {
1569
+ children: [
1570
+ {
1571
+ [Symbol.for('componentType')]: 'Element',
1572
+ children: [
1573
+ {
1574
+ [Symbol.for('componentType')]: 'Page',
1575
+ w: '1920',
1576
+ h: '1080',
1577
+ children: [
1578
+ {
1579
+ [Symbol.for('componentType')]: 'Element',
1580
+ x: '100',
1581
+ y: '$y',
1582
+ slot: 'mySlot',
1583
+ },
1584
+ ],
1585
+ },
1586
+ ],
1587
+ },
1588
+ ],
1589
+ }
1590
+
1591
+ const expectedRender = `
1592
+ function anonymous(parent,component,context) {
1593
+ const elms = []
1594
+
1595
+ if(!elms[0]) {
1596
+ elms[0] = this.element({parent: parent || 'root'}, component)
1597
+ }
1598
+ const elementConfig0 = {}
1599
+
1600
+ if(!elms[0].nodeId) {
1601
+ elms[0].populate(elementConfig0)
1602
+ }
1603
+
1604
+ const cmp1 = (context.components && context.components['Page']) ||
1605
+ component[Symbol.for('components')]['Page']
1606
+
1607
+ parent = elms[0]
1608
+
1609
+ if(!elms[1]) {
1610
+ elms[1] = this.element({parent: parent || 'root'}, component)
1611
+ }
1612
+ const elementConfig1 = {}
1613
+ elementConfig1['w'] = 1920
1614
+ elementConfig1['h'] = 1080
1615
+
1616
+ if(typeof cmp1 !== 'undefined') {
1617
+ for(let key in cmp1.config.props) {
1618
+ delete elementConfig1[cmp1.config.props[key]]
1619
+ }
1620
+ }
1621
+
1622
+ if(!elms[1].nodeId) {
1623
+ elms[1].populate(elementConfig1)
1624
+ }
1625
+
1626
+ parent = elms[1]
1627
+ const props2 = {}
1628
+ props2['w'] = 1920
1629
+ props2['h'] = 1080
1630
+
1631
+
1632
+ if(!elms[2]) {
1633
+ const componentType = props2['is'] || 'Page'
1634
+ elms[2] = (context.components && context.components[componentType] || component[Symbol.for('components')][componentType] || (() => { console.error('component Page not found')})).call(null, {props: props2}, elms[1], component)
1635
+ if (elms[2][Symbol.for('slots')][0]) {
1636
+ parent = elms[2][Symbol.for('slots')][0]
1637
+ component = elms[2]
1638
+ } else {
1639
+ parent = elms[2][Symbol.for('children')][0]
1640
+ }
1641
+ }
1642
+
1643
+ if(!elms[3]) {
1644
+ elms[3] = this.element({parent: parent || 'root'}, component)
1645
+ }
1646
+
1647
+ const elementConfig3 = {}
1648
+
1649
+ if(!elms[3].nodeId) {
1650
+ elms[3].populate(elementConfig3)
1651
+ }
1652
+ parent = elms[3]
1653
+
1654
+
1655
+ if(!elms[4]) {
1656
+ elms[4] = this.element({parent: parent || 'root'}, component)
1657
+ }
1658
+ const elementConfig4 = {}
1659
+ elementConfig4['x'] = 100
1660
+ elementConfig4['y'] = component.y
1661
+
1662
+ elementConfig4['parent'] = component[Symbol.for('slots')].filter(slot => slot.ref === 'mySlot').shift() || component[Symbol.for('slots')][0] || parent
1663
+ elementConfig4['slot'] = "mySlot"
1664
+
1665
+ if(!elms[4].nodeId) {
1666
+ elms[4].populate(elementConfig4)
1667
+ }
1668
+
1669
+ return elms
1670
+ }
1671
+ `
1672
+
1673
+ const actual = generator.call(scope, templateObject)
1674
+
1675
+ assert.equal(
1676
+ normalize(actual.render.toString()),
1677
+ normalize(expectedRender),
1678
+ 'Generator should return a render function with the correct code'
1679
+ )
1680
+
1681
+ assert.ok(
1682
+ Array.isArray(actual.effects) && actual.effects.length === 0,
1683
+ 'Generator should return an empty effects array'
1684
+ )
1685
+
1686
+ assert.end()
1687
+ })
1688
+
1689
+ test('Generate code for a template with a slot', (assert) => {
1690
+ const templateObject = {
1691
+ children: [
1692
+ {
1693
+ [Symbol.for('componentType')]: 'Element',
1694
+ children: [
1695
+ {
1696
+ [Symbol.for('componentType')]: 'Element',
1697
+ w: '1920',
1698
+ h: '1080',
1699
+ children: [
1700
+ {
1701
+ [Symbol.for('componentType')]: 'Slot',
1702
+ x: '100',
1703
+ y: '$y',
1704
+ },
1705
+ ],
1706
+ },
1707
+ ],
1708
+ },
1709
+ ],
1710
+ }
1711
+
1712
+ const expectedRender = `
1713
+ function anonymous(parent,component,context) {
1714
+ const elms = []
1715
+
1716
+ if(!elms[0]) {
1717
+ elms[0] = this.element({parent: parent || 'root'}, component)
1718
+ }
1719
+ const elementConfig0 = {}
1720
+
1721
+ if(!elms[0].nodeId) {
1722
+ elms[0].populate(elementConfig0)
1723
+ }
1724
+
1725
+ parent = elms[0]
1726
+
1727
+ if(!elms[1]) {
1728
+ elms[1] = this.element({parent: parent || 'root'}, component)
1729
+ }
1730
+ const elementConfig1 = {}
1731
+ elementConfig1['w'] = 1920
1732
+ elementConfig1['h'] = 1080
1733
+
1734
+ if(!elms[1].nodeId) {
1735
+ elms[1].populate(elementConfig1)
1736
+ }
1737
+
1738
+ parent = elms[1]
1739
+
1740
+ if(!elms[2]) {
1741
+ elms[2] = this.element({parent: parent || 'root'}, component)
1742
+ }
1743
+
1744
+ const elementConfig2 = {}
1745
+
1746
+ elementConfig2[Symbol.for('isSlot')] = true
1747
+ elementConfig2['x'] = 100
1748
+ elementConfig2['y'] = component.y
1749
+
1750
+ if(!elms[2].nodeId) {
1751
+ elms[2].populate(elementConfig2)
1752
+ }
1753
+
1754
+ return elms
1755
+ }
1756
+ `
1757
+
1758
+ const actual = generator.call(scope, templateObject)
1759
+
1760
+ assert.equal(
1761
+ normalize(actual.render.toString()),
1762
+ normalize(expectedRender),
1763
+ 'Generator should return a render function with the correct code'
1764
+ )
1765
+
1766
+ assert.ok(
1767
+ Array.isArray(actual.effects) && actual.effects.length === 0,
1768
+ 'Generator should return an empty effects array'
1769
+ )
1770
+
1771
+ assert.end()
1772
+ })
1773
+
1774
+ test('Generate code for a template with inline Text', (assert) => {
1775
+ const templateObject = {
1776
+ children: [
1777
+ {
1778
+ [Symbol.for('componentType')]: 'Element',
1779
+ children: [
1780
+ {
1781
+ [Symbol.for('componentType')]: 'Text',
1782
+ content: 'Hello Blits!',
1783
+ },
1784
+ ],
1785
+ },
1786
+ ],
1787
+ }
1788
+
1789
+ const expectedRender = `
1790
+ function anonymous(parent,component,context) {
1791
+ const elms = []
1792
+
1793
+ if(!elms[0]) {
1794
+ elms[0] = this.element({parent: parent || 'root'}, component)
1795
+ }
1796
+ const elementConfig0 = {}
1797
+
1798
+ if(!elms[0].nodeId) {
1799
+ elms[0].populate(elementConfig0)
1800
+ }
1801
+
1802
+ const cmp1 = (context.components && context.components['Text']) ||
1803
+ component[Symbol.for('components')]['Text']
1804
+
1805
+ parent = elms[0]
1806
+ if(!elms[1]) {
1807
+ elms[1] = this.element({parent: parent || 'root'}, component)
1808
+ }
1809
+
1810
+ const elementConfig1 = {}
1811
+ elementConfig1['content'] = 'Hello Blits!'
1812
+ if(typeof cmp1 !== 'undefined') {
1813
+ for(let key in cmp1.config.props) {
1814
+ delete elementConfig1[cmp1.config.props[key]]
1815
+ }
1816
+ }
1817
+
1818
+ if(!elms[1].nodeId) {
1819
+ elms[1].populate(elementConfig1)
1820
+ }
1821
+
1822
+ parent = elms[1]
1823
+ const props2 = {}
1824
+ props2['content'] = 'Hello Blits!'
1825
+
1826
+ if(!elms[2]) {
1827
+ const componentType = props2['is'] || 'Text'
1828
+ elms[2] = (context.components && context.components[componentType] || component[Symbol.for('components')][componentType] || (() => { console.error('component Text not found')})).call(null, {props: props2}, elms[1], component)
1829
+ if (elms[2][Symbol.for('slots')][0]) {
1830
+ parent = elms[2][Symbol.for('slots')][0]
1831
+ component = elms[2]
1832
+ } else {
1833
+ parent = elms[2][Symbol.for('children')][0]
1834
+ }
1835
+ }
1836
+
1837
+ return elms
1838
+ }
1839
+ `
1840
+
1841
+ const actual = generator.call(scope, templateObject)
1842
+
1843
+ assert.equal(
1844
+ normalize(actual.render.toString()),
1845
+ normalize(expectedRender),
1846
+ 'Generator should return a render function with the correct code'
1847
+ )
1848
+ assert.ok(
1849
+ Array.isArray(actual.effects) && actual.effects.length === 0,
1850
+ 'Generator should return an empty effects array'
1851
+ )
1852
+ assert.end()
1853
+ })
1854
+
1855
+ test('Generate code for a template with inline dynamic Text', (assert) => {
1856
+ const templateObject = {
1857
+ children: [
1858
+ {
1859
+ [Symbol.for('componentType')]: 'Element',
1860
+ children: [
1861
+ {
1862
+ [Symbol.for('componentType')]: 'Text',
1863
+ content: '$myText',
1864
+ },
1865
+ ],
1866
+ },
1867
+ ],
1868
+ }
1869
+
1870
+ const expectedRender = `
1871
+ function anonymous(parent,component,context) {
1872
+ const elms = []
1873
+
1874
+ if(!elms[0]) {
1875
+ elms[0] = this.element({parent: parent || 'root'}, component)
1876
+ }
1877
+ const elementConfig0 = {}
1878
+
1879
+ if(!elms[0].nodeId) {
1880
+ elms[0].populate(elementConfig0)
1881
+ }
1882
+
1883
+ const cmp1 = (context.components && context.components['Text']) ||
1884
+ component[Symbol.for('components')]['Text']
1885
+
1886
+ parent = elms[0]
1887
+ if(!elms[1]) {
1888
+ elms[1] = this.element({parent: parent || 'root'}, component)
1889
+ }
1890
+
1891
+ const elementConfig1 = {}
1892
+ elementConfig1['content'] = component.myText
1893
+ if(typeof cmp1 !== 'undefined') {
1894
+ for(let key in cmp1.config.props) {
1895
+ delete elementConfig1[cmp1.config.props[key]]
1896
+ }
1897
+ }
1898
+
1899
+ if(!elms[1].nodeId) {
1900
+ elms[1].populate(elementConfig1)
1901
+ }
1902
+
1903
+ parent = elms[1]
1904
+ const props2 = {}
1905
+ props2['content'] = component.myText
1906
+
1907
+ if(!elms[2]) {
1908
+ const componentType = props2['is'] || 'Text'
1909
+ elms[2] = (context.components && context.components[componentType] || component[Symbol.for('components')][componentType] || (() => { console.error('component Text not found')})).call(null, {props: props2}, elms[1], component)
1910
+ if (elms[2][Symbol.for('slots')][0]) {
1911
+ parent = elms[2][Symbol.for('slots')][0]
1912
+ component = elms[2]
1913
+ } else {
1914
+ parent = elms[2][Symbol.for('children')][0]
1915
+ }
1916
+ }
1917
+
1918
+ return elms
1919
+ }
1920
+ `
1921
+
1922
+ const actual = generator.call(scope, templateObject)
1923
+
1924
+ assert.equal(
1925
+ normalize(actual.render.toString()),
1926
+ normalize(expectedRender),
1927
+ 'Generator should return a render function with the correct code'
1928
+ )
1929
+ assert.ok(
1930
+ Array.isArray(actual.effects) && actual.effects.length === 0,
1931
+ 'Generator should return an empty effects array'
1932
+ )
1933
+ assert.end()
1934
+ })
1935
+
1936
+ test('Generate code for a template with inline dynamic Text embedded in static text', (assert) => {
1937
+ const templateObject = {
1938
+ children: [
1939
+ {
1940
+ [Symbol.for('componentType')]: 'Element',
1941
+ children: [
1942
+ {
1943
+ [Symbol.for('componentType')]: 'Text',
1944
+ content: 'Hello {{$firstname}} {{$lastname}}, how are you?',
1945
+ },
1946
+ ],
1947
+ },
1948
+ ],
1949
+ }
1950
+
1951
+ const expectedRender = `
1952
+ function anonymous(parent,component,context) {
1953
+ const elms = []
1954
+
1955
+ if(!elms[0]) {
1956
+ elms[0] = this.element({parent: parent || 'root'}, component)
1957
+ }
1958
+ const elementConfig0 = {}
1959
+
1960
+ if(!elms[0].nodeId) {
1961
+ elms[0].populate(elementConfig0)
1962
+ }
1963
+
1964
+ const cmp1 = (context.components && context.components['Text']) ||
1965
+ component[Symbol.for('components')]['Text']
1966
+
1967
+ parent = elms[0]
1968
+ if(!elms[1]) {
1969
+ elms[1] = this.element({parent: parent || 'root'}, component)
1970
+ }
1971
+
1972
+ const elementConfig1 = {}
1973
+ elementConfig1['content'] = 'Hello ' + component.firstname + ' ' + component.lastname + ', how are you?'
1974
+ if(typeof cmp1 !== 'undefined') {
1975
+ for(let key in cmp1.config.props) {
1976
+ delete elementConfig1[cmp1.config.props[key]]
1977
+ }
1978
+ }
1979
+
1980
+ if(!elms[1].nodeId) {
1981
+ elms[1].populate(elementConfig1)
1982
+ }
1983
+
1984
+ parent = elms[1]
1985
+ const props2 = {}
1986
+ props2['content'] = 'Hello ' + component.firstname + ' ' + component.lastname + ', how are you?'
1987
+
1988
+ if(!elms[2]) {
1989
+ const componentType = props2['is'] || 'Text'
1990
+ elms[2] = (context.components && context.components[componentType] || component[Symbol.for('components')][componentType] || (() => { console.error('component Text not found')})).call(null, {props: props2}, elms[1], component)
1991
+ if (elms[2][Symbol.for('slots')][0]) {
1992
+ parent = elms[2][Symbol.for('slots')][0]
1993
+ component = elms[2]
1994
+ } else {
1995
+ parent = elms[2][Symbol.for('children')][0]
1996
+ }
1997
+ }
1998
+
1999
+ return elms
2000
+ }
2001
+ `
2002
+
2003
+ const actual = generator.call(scope, templateObject)
2004
+
2005
+ assert.equal(
2006
+ normalize(actual.render.toString()),
2007
+ normalize(expectedRender),
2008
+ 'Generator should return a render function with the correct code'
2009
+ )
2010
+ assert.ok(
2011
+ Array.isArray(actual.effects) && actual.effects.length === 0,
2012
+ 'Generator should return an empty effects array'
2013
+ )
2014
+ assert.end()
2015
+ })
2016
+
2017
+ test('Generate code for a template with a single element with attributes with percentages', (assert) => {
2018
+ const templateObject = {
2019
+ children: [
2020
+ {
2021
+ [Symbol.for('componentType')]: 'Element',
2022
+ w: '1920',
2023
+ h: '1080',
2024
+ children: [
2025
+ {
2026
+ [Symbol.for('componentType')]: 'Element',
2027
+ w: '50%',
2028
+ h: '40%',
2029
+ x: '10%',
2030
+ y: '20%',
2031
+ },
2032
+ ],
2033
+ },
2034
+ ],
2035
+ }
2036
+
2037
+ const expectedRender = `
2038
+ function anonymous(parent,component,context) {
2039
+ const elms = []
2040
+
2041
+ if(!elms[0]) {
2042
+ elms[0] = this.element({parent: parent || 'root'}, component)
2043
+ }
2044
+ const elementConfig0 = {}
2045
+
2046
+ elementConfig0['w'] = 1920
2047
+ elementConfig0['h'] = 1080
2048
+
2049
+ if(!elms[0].nodeId) {
2050
+ elms[0].populate(elementConfig0)
2051
+ }
2052
+
2053
+ parent = elms[0]
2054
+
2055
+ if(!elms[1]) {
2056
+ elms[1] = this.element({parent: parent || 'root'}, component)
2057
+ }
2058
+ const elementConfig1 = {}
2059
+
2060
+ elementConfig1['w'] = parent.node.width * (50/100)
2061
+ elementConfig1['h'] = parent.node.height * (40/100)
2062
+ elementConfig1['x'] = parent.node.width * (10/100)
2063
+ elementConfig1['y'] = parent.node.height * (20/100)
2064
+
2065
+ if(!elms[1].nodeId) {
2066
+ elms[1].populate(elementConfig1)
2067
+ }
2068
+
2069
+ return elms
2070
+ }
2071
+ `
2072
+
2073
+ const actual = generator.call(scope, templateObject)
2074
+
2075
+ assert.equal(
2076
+ normalize(actual.render.toString()),
2077
+ normalize(expectedRender),
2078
+ 'Generator should return a render function with the correct code'
2079
+ )
2080
+ assert.ok(
2081
+ Array.isArray(actual.effects) && actual.effects.length === 0,
2082
+ 'Generator should return an empty effects array'
2083
+ )
2084
+ assert.end()
2085
+ })
2086
+
2087
+ test('Generate code for a template with a simple for-loop on an Element', (assert) => {
2088
+ const templateObject = {
2089
+ children: [
2090
+ {
2091
+ [Symbol.for('componentType')]: 'Element',
2092
+ children: [
2093
+ {
2094
+ [Symbol.for('componentType')]: 'Element',
2095
+ ':for': 'item in $items',
2096
+ },
2097
+ ],
2098
+ },
2099
+ ],
2100
+ }
2101
+
2102
+ const expectedRender = `
2103
+ function anonymous(parent,component,context) {
2104
+ const elms = []
2105
+
2106
+ if(!elms[0]) {
2107
+ elms[0] = this.element({parent: parent || 'root'}, component)
2108
+ }
2109
+ const elementConfig0 = {}
2110
+
2111
+ if(!elms[0].nodeId) {
2112
+ elms[0].populate(elementConfig0)
2113
+ }
2114
+
2115
+ return elms
2116
+ }
2117
+ `
2118
+
2119
+ const expectedEffect1 = `
2120
+ function anonymous(component,elms,context) {
2121
+ parent = elms[0]
2122
+ const collection = component.items
2123
+ const keys = []
2124
+ for(let __index = 0; __index < collection.length; __index++) {
2125
+ parent = elms[0]
2126
+ if(!component.key) keys.length = 0
2127
+ const scope = Object.assign(component, {
2128
+ key: Math.random(),
2129
+ index: __index,
2130
+ item: collection[__index],
2131
+ })
2132
+ keys.push(scope.key.toString())
2133
+
2134
+ parent = elms[0]
2135
+ elms[1] = elms[1] || {}
2136
+ if(!elms[1][scope.key]) {
2137
+ elms[1][scope.key] = this.element({parent: parent || 'root'}, component)
2138
+ }
2139
+
2140
+ const elementConfig1 = {}
2141
+ if(!elms[1][scope.key].nodeId) {
2142
+ elms[1][scope.key].populate(elementConfig1)
2143
+ }
2144
+ }
2145
+ if(elms[1]) {
2146
+ Object.keys(elms[1]).forEach(key => {
2147
+ if(keys.indexOf(key) === -1) {
2148
+ elms[1][key].delete && elms[1][key].delete()
2149
+ elms[1][key].destroy && elms[1][key].destroy()
2150
+ delete elms[1][key]
2151
+ }
2152
+ })
2153
+ }
2154
+ }
2155
+ `
2156
+
2157
+ const actual = generator.call(scope, templateObject)
2158
+
2159
+ assert.equal(
2160
+ normalize(actual.render.toString()),
2161
+ normalize(expectedRender),
2162
+ 'Generator should return a render function with the correct code'
2163
+ )
2164
+
2165
+ assert.ok(
2166
+ Array.isArray(actual.effects) && actual.effects.length === 1,
2167
+ 'Generator should return an effects array with 1 function'
2168
+ )
2169
+
2170
+ assert.equal(
2171
+ normalize(actual.effects[0].toString()),
2172
+ normalize(expectedEffect1),
2173
+ 'Generator should return an effect function for the for loop'
2174
+ )
2175
+
2176
+ assert.end()
2177
+ })
2178
+
2179
+ test('Generate code for a template with a simple for-loop on an Element with a custom index key', (assert) => {
2180
+ const templateObject = {
2181
+ children: [
2182
+ {
2183
+ [Symbol.for('componentType')]: 'Element',
2184
+ children: [
2185
+ {
2186
+ [Symbol.for('componentType')]: 'Element',
2187
+ ':for': '(item, customIndex) in $items',
2188
+ },
2189
+ ],
2190
+ },
2191
+ ],
2192
+ }
2193
+
2194
+ const expectedRender = `
2195
+ function anonymous(parent,component,context) {
2196
+ const elms = []
2197
+
2198
+ if(!elms[0]) {
2199
+ elms[0] = this.element({parent: parent || 'root'}, component)
2200
+ }
2201
+ const elementConfig0 = {}
2202
+
2203
+ if(!elms[0].nodeId) {
2204
+ elms[0].populate(elementConfig0)
2205
+ }
2206
+
2207
+ return elms
2208
+ }
2209
+ `
2210
+
2211
+ const expectedEffect1 = `
2212
+ function anonymous(component,elms,context) {
2213
+ parent = elms[0]
2214
+ const collection = component.items
2215
+ const keys = []
2216
+ for(let __index = 0; __index < collection.length; __index++) {
2217
+ parent = elms[0]
2218
+ if(!component.key) keys.length = 0
2219
+ const scope = Object.assign(component, {
2220
+ key: Math.random(),
2221
+ customIndex: __index,
2222
+ item: collection[__index],
2223
+ })
2224
+ keys.push(scope.key.toString())
2225
+
2226
+ parent = elms[0]
2227
+ elms[1] = elms[1] || {}
2228
+ if(!elms[1][scope.key]) {
2229
+ elms[1][scope.key] = this.element({parent: parent || 'root'}, component)
2230
+ }
2231
+
2232
+ const elementConfig1 = {}
2233
+ if(!elms[1][scope.key].nodeId) {
2234
+ elms[1][scope.key].populate(elementConfig1)
2235
+ }
2236
+ }
2237
+ if(elms[1]) {
2238
+ Object.keys(elms[1]).forEach(key => {
2239
+ if(keys.indexOf(key) === -1) {
2240
+ elms[1][key].delete && elms[1][key].delete()
2241
+ elms[1][key].destroy && elms[1][key].destroy()
2242
+ delete elms[1][key]
2243
+ }
2244
+ })
2245
+ }
2246
+ }
2247
+ `
2248
+
2249
+ const actual = generator.call(scope, templateObject)
2250
+
2251
+ assert.equal(
2252
+ normalize(actual.render.toString()),
2253
+ normalize(expectedRender),
2254
+ 'Generator should return a render function with the correct code'
2255
+ )
2256
+
2257
+ assert.ok(
2258
+ Array.isArray(actual.effects) && actual.effects.length === 1,
2259
+ 'Generator should return an effects array with 1 function'
2260
+ )
2261
+
2262
+ assert.equal(
2263
+ normalize(actual.effects[0].toString()),
2264
+ normalize(expectedEffect1),
2265
+ 'Generator should return an effect function for the for loop'
2266
+ )
2267
+
2268
+ assert.end()
2269
+ })
2270
+
2271
+ test('Generate code for a template with a simple for-loop on an Element with a key attribute', (assert) => {
2272
+ const templateObject = {
2273
+ children: [
2274
+ {
2275
+ [Symbol.for('componentType')]: 'Element',
2276
+ children: [
2277
+ {
2278
+ [Symbol.for('componentType')]: 'Element',
2279
+ ':for': 'item in $items',
2280
+ key: '$item.id',
2281
+ },
2282
+ ],
2283
+ },
2284
+ ],
2285
+ }
2286
+
2287
+ const expectedRender = `
2288
+ function anonymous(parent,component,context) {
2289
+ const elms = []
2290
+
2291
+ if(!elms[0]) {
2292
+ elms[0] = this.element({parent: parent || 'root'}, component)
2293
+ }
2294
+ const elementConfig0 = {}
2295
+
2296
+ if(!elms[0].nodeId) {
2297
+ elms[0].populate(elementConfig0)
2298
+ }
2299
+
2300
+ return elms
2301
+ }
2302
+ `
2303
+
2304
+ const expectedEffect1 = `
2305
+ function anonymous(component,elms,context) {
2306
+ parent = elms[0]
2307
+ const collection = component.items
2308
+ const keys = []
2309
+ for(let __index = 0; __index < collection.length; __index++) {
2310
+ parent = elms[0]
2311
+ if(!component.key) keys.length = 0
2312
+ const scope = Object.assign(component, {
2313
+ key: Math.random(),
2314
+ index: __index,
2315
+ item: collection[__index],
2316
+ })
2317
+ keys.push(item.id)
2318
+
2319
+ parent = elms[0]
2320
+ elms[1] = elms[1] || {}
2321
+ if(!elms[1][item.id]) {
2322
+ elms[1][item.id] = this.element({parent: parent || 'root'}, component)
2323
+ }
2324
+
2325
+ const elementConfig1 = {}
2326
+ elementConfig1['key'] = "item.id"
2327
+
2328
+ if(!elms[1][item.id].nodeId) {
2329
+ elms[1][item.id].populate(elementConfig1)
2330
+ }
2331
+ }
2332
+ if(elms[1]) {
2333
+ Object.keys(elms[1]).forEach(key => {
2334
+ if(keys.indexOf(key) === -1) {
2335
+ elms[1][key].delete && elms[1][key].delete()
2336
+ elms[1][key].destroy && elms[1][key].destroy()
2337
+ delete elms[1][key]
2338
+ }
2339
+ })
2340
+ }
2341
+ }
2342
+ `
2343
+
2344
+ const actual = generator.call(scope, templateObject)
2345
+
2346
+ assert.equal(
2347
+ normalize(actual.render.toString()),
2348
+ normalize(expectedRender),
2349
+ 'Generator should return a render function with the correct code'
2350
+ )
2351
+
2352
+ assert.ok(
2353
+ Array.isArray(actual.effects) && actual.effects.length === 1,
2354
+ 'Generator should return an effects array with 1 function'
2355
+ )
2356
+
2357
+ assert.equal(
2358
+ normalize(actual.effects[0].toString()),
2359
+ normalize(expectedEffect1),
2360
+ 'Generator should return an effect function for the for loop'
2361
+ )
2362
+
2363
+ assert.end()
2364
+ })
2365
+
2366
+ test('Generate code for a template with a simple for-loop on a Component with a key attribute and a custom index', (assert) => {
2367
+ const templateObject = {
2368
+ children: [
2369
+ {
2370
+ [Symbol.for('componentType')]: 'Element',
2371
+ children: [
2372
+ {
2373
+ [Symbol.for('componentType')]: 'ListItem',
2374
+ ':for': '(item, myIndex) in $items',
2375
+ key: 'item.id',
2376
+ },
2377
+ ],
2378
+ },
2379
+ ],
2380
+ }
2381
+
2382
+ const expectedRender = `
2383
+ function anonymous(parent,component,context) {
2384
+ const elms = []
2385
+
2386
+ if(!elms[0]) {
2387
+ elms[0] = this.element({parent: parent || 'root'}, component)
2388
+ }
2389
+ const elementConfig0 = {}
2390
+
2391
+ if(!elms[0].nodeId) {
2392
+ elms[0].populate(elementConfig0)
2393
+ }
2394
+
2395
+ return elms
2396
+ }
2397
+ `
2398
+
2399
+ const expectedEffect1 = `
2400
+ function anonymous(component,elms,context) {
2401
+ parent = elms[0]
2402
+ const collection = component.items
2403
+ const keys = []
2404
+ for(let __index = 0; __index < collection.length; __index++) {
2405
+ parent = elms[0]
2406
+ if(!component.key) keys.length = 0
2407
+ const scope = Object.assign(component, {
2408
+ key: Math.random(),
2409
+ myIndex: __index,
2410
+ item: collection[__index],
2411
+ })
2412
+ keys.push(item.id)
2413
+
2414
+ const cmp1 = (context.components && context.components['ListItem']) ||
2415
+ component[Symbol.for('components')]['ListItem']
2416
+
2417
+ elms[1] = elms[1] || {}
2418
+ if(!elms[1][item.id]) {
2419
+ elms[1][item.id] = this.element({parent: parent || 'root'}, component)
2420
+ }
2421
+
2422
+ const elementConfig1 = {}
2423
+ elementConfig1['key'] = "item.id"
2424
+
2425
+ if(typeof cmp1 !== 'undefined') {
2426
+ for(let key in cmp1.config.props) {
2427
+ delete elementConfig1[cmp1.config.props[key]]
2428
+ }
2429
+ }
2430
+
2431
+ if(!elms[1][item.id].nodeId) {
2432
+ elms[1][item.id].populate(elementConfig1)
2433
+ }
2434
+
2435
+ elms[2] = elms[2] || {}
2436
+ parent = elms[1][item.id]
2437
+
2438
+ const props2 = {}
2439
+ props2['key'] = "item.id"
2440
+
2441
+ if(!elms[2][item.id]) {
2442
+ const componentType = props2['is'] || 'ListItem'
2443
+ elms[2][item.id] = (context.components && context.components[componentType] || component[Symbol.for('components')][componentType] || (() => { console.error('component ListItem not found')})).call(null, {props: props2}, elms[1][item.id], component)
2444
+ if (elms[2][item.id][Symbol.for('slots')][0]) {
2445
+ parent = elms[2][item.id][Symbol.for('slots')][0]
2446
+ component = elms[2][item.id]
2447
+ } else {
2448
+ parent = elms[2][item.id][Symbol.for('children')][0]
2449
+ }
2450
+ }
2451
+ }
2452
+ if(elms[2]) {
2453
+ Object.keys(elms[2]).forEach(key => {
2454
+ if(keys.indexOf(key) === -1) {
2455
+ elms[2][key].delete && elms[2][key].delete()
2456
+ elms[2][key].destroy && elms[2][key].destroy()
2457
+ delete elms[2][key]
2458
+ }
2459
+ })
2460
+ }
2461
+ }
2462
+ `
2463
+
2464
+ const actual = generator.call(scope, templateObject)
2465
+
2466
+ assert.equal(
2467
+ normalize(actual.render.toString()),
2468
+ normalize(expectedRender),
2469
+ 'Generator should return a render function with the correct code'
2470
+ )
2471
+
2472
+ assert.ok(
2473
+ Array.isArray(actual.effects) && actual.effects.length === 1,
2474
+ 'Generator should return an effects array with 1 function'
2475
+ )
2476
+
2477
+ assert.equal(
2478
+ normalize(actual.effects[0].toString()),
2479
+ normalize(expectedEffect1),
2480
+ 'Generator should return an effect function for the for loop'
2481
+ )
2482
+
2483
+ assert.end()
2484
+ })
2485
+
2486
+ test('Generate code for a template with a simple for-loop on an Element with an automatic ref attribute', (assert) => {
2487
+ const templateObject = {
2488
+ children: [
2489
+ {
2490
+ [Symbol.for('componentType')]: 'Element',
2491
+ children: [
2492
+ {
2493
+ [Symbol.for('componentType')]: 'Element',
2494
+ ':for': 'item in $items',
2495
+ key: 'item.id',
2496
+ ref: 'myref',
2497
+ },
2498
+ ],
2499
+ },
2500
+ ],
2501
+ }
2502
+
2503
+ const expectedRender = `
2504
+ function anonymous(parent,component,context) {
2505
+ const elms = []
2506
+
2507
+ if(!elms[0]) {
2508
+ elms[0] = this.element({parent: parent || 'root'}, component)
2509
+ }
2510
+ const elementConfig0 = {}
2511
+
2512
+ if(!elms[0].nodeId) {
2513
+ elms[0].populate(elementConfig0)
2514
+ }
2515
+
2516
+ return elms
2517
+ }
2518
+ `
2519
+
2520
+ const expectedEffect1 = `
2521
+ function anonymous(component,elms,context) {
2522
+ parent = elms[0]
2523
+ const collection = component.items
2524
+ const keys = []
2525
+ for(let __index = 0; __index < collection.length; __index++) {
2526
+ parent = elms[0]
2527
+ if(!component.key) keys.length = 0
2528
+ const scope = Object.assign(component, {
2529
+ key: Math.random(),
2530
+ index: __index,
2531
+ item: collection[__index],
2532
+ __ref: 'myref'+__index
2533
+ })
2534
+ keys.push(item.id)
2535
+
2536
+ parent = elms[0]
2537
+ elms[1] = elms[1] || {}
2538
+ if(!elms[1][item.id]) {
2539
+ elms[1][item.id] = this.element({parent: parent || 'root'}, component)
2540
+ }
2541
+
2542
+ const elementConfig1 = {}
2543
+ elementConfig1['key'] = "item.id"
2544
+ elementConfig1['ref'] = scope.__ref
2545
+
2546
+ if(!elms[1][item.id].nodeId) {
2547
+ elms[1][item.id].populate(elementConfig1)
2548
+ }
2549
+ }
2550
+ if(elms[1]) {
2551
+ Object.keys(elms[1]).forEach(key => {
2552
+ if(keys.indexOf(key) === -1) {
2553
+ elms[1][key].delete && elms[1][key].delete()
2554
+ elms[1][key].destroy && elms[1][key].destroy()
2555
+ delete elms[1][key]
2556
+ }
2557
+ })
2558
+ }
2559
+ }
2560
+ `
2561
+
2562
+ const actual = generator.call(scope, templateObject)
2563
+
2564
+ assert.equal(
2565
+ normalize(actual.render.toString()),
2566
+ normalize(expectedRender),
2567
+ 'Generator should return a render function with the correct code'
2568
+ )
2569
+
2570
+ assert.ok(
2571
+ Array.isArray(actual.effects) && actual.effects.length === 1,
2572
+ 'Generator should return an effects array with 1 function'
2573
+ )
2574
+
2575
+ assert.equal(
2576
+ normalize(actual.effects[0].toString()),
2577
+ normalize(expectedEffect1),
2578
+ 'Generator should return an effect function for the for loop'
2579
+ )
2580
+
2581
+ assert.end()
2582
+ })