@player-ui/player 0.11.0-next.3 → 0.11.0-next.4

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.
@@ -9,6 +9,7 @@ import { ViewInstance } from "../../view";
9
9
  import type { Options } from "../options";
10
10
  import { TemplatePlugin, MultiNodePlugin, AssetPlugin } from "../";
11
11
  import { StringResolverPlugin, toNodeResolveOptions } from "../..";
12
+ import type { View } from "@player-ui/types";
12
13
 
13
14
  const templateJoinValues = {
14
15
  id: "generated-flow",
@@ -29,6 +30,13 @@ const templateJoinValues = {
29
30
  },
30
31
  },
31
32
  ],
33
+ label: {
34
+ asset: {
35
+ id: "label-test",
36
+ value: "A text label",
37
+ type: "text",
38
+ },
39
+ },
32
40
  values: [
33
41
  {
34
42
  asset: {
@@ -65,6 +73,13 @@ const templateJoinValues = {
65
73
  },
66
74
  },
67
75
  ],
76
+ label: {
77
+ asset: {
78
+ id: "label-test",
79
+ value: "A text label",
80
+ type: "text",
81
+ },
82
+ },
68
83
  template: [
69
84
  {
70
85
  data: "foo",
@@ -150,7 +165,7 @@ describe("templates", () => {
150
165
  ).toMatchSnapshot();
151
166
  });
152
167
 
153
- it("works with nested templates", () => {
168
+ it("works with static nested templates", () => {
154
169
  const petNames = ["Ginger", "Daisy", "Afra"];
155
170
  model.set([["foo.pets", petNames]]);
156
171
 
@@ -185,153 +200,152 @@ describe("templates", () => {
185
200
  }),
186
201
  ).toMatchSnapshot();
187
202
  });
188
- });
189
203
 
190
- describe("dynamic templates", () => {
191
- it("static - nodes are not updated", () => {
192
- const petNames = ["Ginger", "Vokey"];
193
- const model = withParser(new LocalModel({}), parseBinding);
194
- const evaluator = new ExpressionEvaluator({ model });
195
- const schema = new SchemaController();
204
+ describe("works with data changes as expected", () => {
205
+ it("static - nodes are not updated", () => {
206
+ const petNames = ["Ginger", "Vokey"];
207
+ const model = withParser(new LocalModel({}), parseBinding);
208
+ const evaluator = new ExpressionEvaluator({ model });
209
+ const schema = new SchemaController();
196
210
 
197
- const view = new ViewInstance(
198
- {
199
- id: "my-view",
200
- asset: {
201
- id: "foo",
202
- type: "collection",
203
- template: [
204
- {
205
- dynamic: false,
206
- data: "foo.bar",
207
- output: "values",
208
- value: {
209
- value: "{{foo.bar._index_}}",
211
+ const view = new ViewInstance(
212
+ {
213
+ id: "my-view",
214
+ asset: {
215
+ id: "foo",
216
+ type: "collection",
217
+ template: [
218
+ {
219
+ dynamic: false,
220
+ data: "foo.bar",
221
+ output: "values",
222
+ value: {
223
+ value: "{{foo.bar._index_}}",
224
+ },
210
225
  },
211
- },
212
- ],
226
+ ],
227
+ },
228
+ } as any,
229
+ {
230
+ model,
231
+ parseBinding,
232
+ evaluator,
233
+ schema,
213
234
  },
214
- } as any,
215
- {
216
- model,
217
- parseBinding,
218
- evaluator,
219
- schema,
220
- },
221
- );
222
-
223
- const pluginOptions = toNodeResolveOptions(view.resolverOptions);
224
- new TemplatePlugin(pluginOptions).apply(view);
225
- new StringResolverPlugin().apply(view);
226
-
227
- model.set([["foo.bar", petNames]]);
235
+ );
228
236
 
229
- const resolved = view.update();
237
+ const pluginOptions = toNodeResolveOptions(view.resolverOptions);
238
+ new TemplatePlugin(pluginOptions).apply(view);
239
+ new StringResolverPlugin().apply(view);
230
240
 
231
- expect(resolved).toStrictEqual({
232
- id: "my-view",
233
- asset: {
234
- id: "foo",
235
- type: "collection",
236
- values: ["Ginger", "Vokey"].map((value) => ({ value })),
237
- },
238
- });
241
+ model.set([["foo.bar", petNames]]);
239
242
 
240
- model.set([["foo.bar", ["Ginger", "Vokey", "Harry"]]]);
243
+ const resolved = view.update();
241
244
 
242
- let updated = view.update();
243
- expect(updated).toStrictEqual({
244
- id: "my-view",
245
- asset: {
246
- id: "foo",
247
- type: "collection",
248
- values: ["Ginger", "Vokey"].map((value) => ({ value })),
249
- },
250
- });
245
+ expect(resolved).toStrictEqual({
246
+ id: "my-view",
247
+ asset: {
248
+ id: "foo",
249
+ type: "collection",
250
+ values: ["Ginger", "Vokey"].map((value) => ({ value })),
251
+ },
252
+ });
251
253
 
252
- model.set([["foo.bar", ["Ginger"]]]);
253
- updated = view.update();
254
- expect(updated).toStrictEqual({
255
- id: "my-view",
256
- asset: {
257
- id: "foo",
258
- type: "collection",
259
- values: ["Ginger", undefined].map((value) => ({ value })),
260
- },
261
- });
262
- });
254
+ model.set([["foo.bar", ["Ginger", "Vokey", "Harry"]]]);
263
255
 
264
- it("dynamic - nodes are updated", () => {
265
- const petNames = ["Ginger", "Vokey"];
266
- const model = withParser(new LocalModel({}), parseBinding);
267
- const evaluator = new ExpressionEvaluator({ model });
268
- const schema = new SchemaController();
256
+ let updated = view.update();
257
+ expect(updated).toStrictEqual({
258
+ id: "my-view",
259
+ asset: {
260
+ id: "foo",
261
+ type: "collection",
262
+ values: ["Ginger", "Vokey"].map((value) => ({ value })),
263
+ },
264
+ });
269
265
 
270
- const view = new ViewInstance(
271
- {
266
+ model.set([["foo.bar", ["Ginger"]]]);
267
+ updated = view.update();
268
+ expect(updated).toStrictEqual({
272
269
  id: "my-view",
273
270
  asset: {
274
271
  id: "foo",
275
272
  type: "collection",
276
- template: [
277
- {
278
- dynamic: true,
279
- data: "foo.bar",
280
- output: "values",
281
- value: {
282
- value: "{{foo.bar._index_}}",
273
+ values: ["Ginger", undefined].map((value) => ({ value })),
274
+ },
275
+ });
276
+ });
277
+ it("dynamic - nodes are updated", () => {
278
+ const petNames = ["Ginger", "Vokey"];
279
+ const model = withParser(new LocalModel({}), parseBinding);
280
+ const evaluator = new ExpressionEvaluator({ model });
281
+ const schema = new SchemaController();
282
+
283
+ const view = new ViewInstance(
284
+ {
285
+ id: "my-view",
286
+ asset: {
287
+ id: "foo",
288
+ type: "collection",
289
+ template: [
290
+ {
291
+ dynamic: true,
292
+ data: "foo.bar",
293
+ output: "values",
294
+ value: {
295
+ value: "{{foo.bar._index_}}",
296
+ },
283
297
  },
284
- },
285
- ],
298
+ ],
299
+ },
300
+ } as any,
301
+ {
302
+ model,
303
+ parseBinding,
304
+ evaluator,
305
+ schema,
286
306
  },
287
- } as any,
288
- {
289
- model,
290
- parseBinding,
291
- evaluator,
292
- schema,
293
- },
294
- );
307
+ );
295
308
 
296
- const pluginOptions = toNodeResolveOptions(view.resolverOptions);
297
- new TemplatePlugin(pluginOptions).apply(view);
298
- new StringResolverPlugin().apply(view);
309
+ const pluginOptions = toNodeResolveOptions(view.resolverOptions);
310
+ new TemplatePlugin(pluginOptions).apply(view);
311
+ new StringResolverPlugin().apply(view);
299
312
 
300
- model.set([["foo.bar", petNames]]);
313
+ model.set([["foo.bar", petNames]]);
301
314
 
302
- const resolved = view.update();
315
+ const resolved = view.update();
303
316
 
304
- expect(resolved).toStrictEqual({
305
- id: "my-view",
306
- asset: {
307
- id: "foo",
308
- type: "collection",
309
- values: ["Ginger", "Vokey"].map((value) => ({ value })),
310
- },
311
- });
317
+ expect(resolved).toStrictEqual({
318
+ id: "my-view",
319
+ asset: {
320
+ id: "foo",
321
+ type: "collection",
322
+ values: ["Ginger", "Vokey"].map((value) => ({ value })),
323
+ },
324
+ });
312
325
 
313
- const barBinding = parseBinding("foo.bar");
314
- model.set([[barBinding, ["Vokey", "Louis", "Bob"]]]);
326
+ const barBinding = parseBinding("foo.bar");
327
+ model.set([[barBinding, ["Vokey", "Louis", "Bob"]]]);
315
328
 
316
- let updated = view.update();
317
- expect(updated).toStrictEqual({
318
- id: "my-view",
319
- asset: {
320
- id: "foo",
321
- type: "collection",
322
- values: ["Vokey", "Louis", "Bob"].map((value) => ({ value })),
323
- },
324
- });
329
+ let updated = view.update();
330
+ expect(updated).toStrictEqual({
331
+ id: "my-view",
332
+ asset: {
333
+ id: "foo",
334
+ type: "collection",
335
+ values: ["Vokey", "Louis", "Bob"].map((value) => ({ value })),
336
+ },
337
+ });
325
338
 
326
- model.set([[barBinding, ["Nuri"]]]);
327
- updated = view.update();
328
- expect(updated).toStrictEqual({
329
- id: "my-view",
330
- asset: {
331
- id: "foo",
332
- type: "collection",
333
- values: ["Nuri"].map((value) => ({ value })),
334
- },
339
+ model.set([[barBinding, ["Nuri"]]]);
340
+ updated = view.update();
341
+ expect(updated).toStrictEqual({
342
+ id: "my-view",
343
+ asset: {
344
+ id: "foo",
345
+ type: "collection",
346
+ values: ["Nuri"].map((value) => ({ value })),
347
+ },
348
+ });
335
349
  });
336
350
  });
337
351
 
@@ -342,8 +356,8 @@ describe("dynamic templates", () => {
342
356
  );
343
357
  const evaluator = new ExpressionEvaluator({ model });
344
358
 
345
- it("Should show template item first when coming before values on lexical order", () => {
346
- const view = new ViewInstance(templateJoinValues.views[0], {
359
+ it("Should show static template item first when coming before values on lexical order", () => {
360
+ const view = new ViewInstance(templateJoinValues.views[0] as View, {
347
361
  model,
348
362
  parseBinding,
349
363
  evaluator,
@@ -361,8 +375,8 @@ describe("dynamic templates", () => {
361
375
  expect(resolved.values).toHaveLength(4);
362
376
  expect(resolved.values).toMatchSnapshot();
363
377
  });
364
- it("Should show template item last when coming after values on lexical order", () => {
365
- const view = new ViewInstance(templateJoinValues.views[1], {
378
+ it("Should show static template item last when coming after values on lexical order", () => {
379
+ const view = new ViewInstance(templateJoinValues.views[1] as View, {
366
380
  model,
367
381
  parseBinding,
368
382
  evaluator,
@@ -380,5 +394,455 @@ describe("dynamic templates", () => {
380
394
  expect(resolved.values).toHaveLength(4);
381
395
  expect(resolved.values).toMatchSnapshot();
382
396
  });
397
+ it("Should show static template items last when using placement append", () => {
398
+ const viewWithAppend = {
399
+ ...templateJoinValues.views[0],
400
+ template: [
401
+ {
402
+ ...templateJoinValues.views[0]?.template[0],
403
+ placement: "append",
404
+ },
405
+ ],
406
+ };
407
+
408
+ const view = new ViewInstance(viewWithAppend as View, {
409
+ model,
410
+ parseBinding,
411
+ evaluator,
412
+ schema: new SchemaController(),
413
+ });
414
+
415
+ const pluginOptions = toNodeResolveOptions(view.resolverOptions);
416
+ new AssetPlugin().apply(view);
417
+ new TemplatePlugin(pluginOptions).apply(view);
418
+ new StringResolverPlugin().apply(view);
419
+ new MultiNodePlugin().apply(view);
420
+
421
+ const resolved = view.update();
422
+
423
+ // Verify the order: first the non-template values, then the template values
424
+ expect(resolved.values[0].asset.id).toBe("value-2");
425
+ expect(resolved.values[0].asset.value).toBe(
426
+ "First value in the collection",
427
+ );
428
+
429
+ expect(resolved.values[1].asset.id).toBe("value-3");
430
+ expect(resolved.values[1].asset.value).toBe(
431
+ "Second value in the collection",
432
+ );
433
+
434
+ expect(resolved.values).toHaveLength(4);
435
+ expect(resolved.values).toMatchSnapshot();
436
+ });
437
+
438
+ it("Should show static template items first when using placement prepend", () => {
439
+ const viewWithPrepend = {
440
+ ...templateJoinValues.views[1],
441
+ template: [
442
+ {
443
+ ...templateJoinValues.views[1]?.template[0],
444
+ placement: "prepend",
445
+ },
446
+ ],
447
+ };
448
+
449
+ const view = new ViewInstance(viewWithPrepend as View, {
450
+ model,
451
+ parseBinding,
452
+ evaluator,
453
+ schema: new SchemaController(),
454
+ });
455
+
456
+ const pluginOptions = toNodeResolveOptions(view.resolverOptions);
457
+ new AssetPlugin().apply(view);
458
+ new TemplatePlugin(pluginOptions).apply(view);
459
+ new StringResolverPlugin().apply(view);
460
+ new MultiNodePlugin().apply(view);
461
+
462
+ const resolved = view.update();
463
+
464
+ // Verify the order: first the template values, then the non-template values
465
+ expect(resolved.values[0].asset.id).toBe("value-0");
466
+ expect(resolved.values[0].asset.value).toBe("item 1");
467
+
468
+ expect(resolved.values[1].asset.id).toBe("value-1");
469
+ expect(resolved.values[1].asset.value).toBe("item 2");
470
+
471
+ expect(resolved.values).toHaveLength(4);
472
+ expect(resolved.values).toMatchSnapshot();
473
+ });
474
+ it("Should show dynamic template items last when using placement append", () => {
475
+ const petNames = ["Ginger", "Vokey"];
476
+ const model = withParser(new LocalModel({}), parseBinding);
477
+ const evaluator = new ExpressionEvaluator({ model });
478
+ const schema = new SchemaController();
479
+
480
+ const view = new ViewInstance(
481
+ {
482
+ id: "my-view",
483
+ asset: {
484
+ id: "foo",
485
+ type: "collection",
486
+ template: [
487
+ {
488
+ dynamic: true,
489
+ data: "foo.bar",
490
+ output: "values",
491
+ placement: "append",
492
+ value: {
493
+ asset: {
494
+ id: "dynamic-_index_",
495
+ type: "text",
496
+ value: "item {{foo.bar._index_}}",
497
+ },
498
+ },
499
+ },
500
+ ],
501
+ label: {
502
+ asset: {
503
+ id: "label-test",
504
+ value: "A text label",
505
+ type: "text",
506
+ },
507
+ },
508
+ values: [
509
+ {
510
+ asset: {
511
+ id: "value-1",
512
+ type: "text",
513
+ value: "First value in the collection",
514
+ },
515
+ },
516
+ {
517
+ asset: {
518
+ id: "value-2",
519
+ type: "text",
520
+ value: "Second value in the collection",
521
+ },
522
+ },
523
+ ],
524
+ },
525
+ } as any,
526
+ {
527
+ model,
528
+ parseBinding,
529
+ evaluator,
530
+ schema,
531
+ },
532
+ );
533
+
534
+ const pluginOptions = toNodeResolveOptions(view.resolverOptions);
535
+ new TemplatePlugin(pluginOptions).apply(view);
536
+ new StringResolverPlugin().apply(view);
537
+ new AssetPlugin().apply(view);
538
+ new MultiNodePlugin().apply(view);
539
+
540
+ model.set([["foo.bar", petNames]]);
541
+
542
+ const resolved = view.update();
543
+ expect(resolved.asset.values).toHaveLength(4);
544
+ expect(resolved.asset.values[2].asset.value).toBe("item Ginger");
545
+ expect(resolved.asset.values[3].asset.value).toBe("item Vokey");
546
+
547
+ const barBinding = parseBinding("foo.bar");
548
+ model.set([[barBinding, ["Louis", "Bob", "Nuri"]]]);
549
+
550
+ const updated = view.update();
551
+ expect(updated.asset.values).toHaveLength(5);
552
+ expect(updated.asset.values[2].asset.value).toBe("item Louis");
553
+ expect(updated.asset.values[3].asset.value).toBe("item Bob");
554
+ expect(updated.asset.values[4].asset.value).toBe("item Nuri");
555
+ });
556
+ it("Should show dynamic template items first when using placement prepend", () => {
557
+ const petNames = ["Ginger", "Vokey"];
558
+ const model = withParser(new LocalModel({}), parseBinding);
559
+ const evaluator = new ExpressionEvaluator({ model });
560
+ const schema = new SchemaController();
561
+
562
+ const view = new ViewInstance(
563
+ {
564
+ id: "my-view",
565
+ asset: {
566
+ id: "foo",
567
+ type: "collection",
568
+ values: [
569
+ {
570
+ asset: {
571
+ id: "value-1",
572
+ type: "text",
573
+ value: "First value in the collection",
574
+ },
575
+ },
576
+ {
577
+ asset: {
578
+ id: "value-2",
579
+ type: "text",
580
+ value: "Second value in the collection",
581
+ },
582
+ },
583
+ ],
584
+ label: {
585
+ asset: {
586
+ id: "label-test",
587
+ value: "A text label",
588
+ type: "text",
589
+ },
590
+ },
591
+ template: [
592
+ {
593
+ dynamic: true,
594
+ data: "foo.bar",
595
+ output: "values",
596
+ placement: "prepend",
597
+ value: {
598
+ asset: {
599
+ id: "dynamic-_index_",
600
+ type: "text",
601
+ value: "item {{foo.bar._index_}}",
602
+ },
603
+ },
604
+ },
605
+ ],
606
+ },
607
+ } as any,
608
+ {
609
+ model,
610
+ parseBinding,
611
+ evaluator,
612
+ schema,
613
+ },
614
+ );
615
+
616
+ const pluginOptions = toNodeResolveOptions(view.resolverOptions);
617
+ new TemplatePlugin(pluginOptions).apply(view);
618
+ new StringResolverPlugin().apply(view);
619
+ new AssetPlugin().apply(view);
620
+ new MultiNodePlugin().apply(view);
621
+
622
+ model.set([["foo.bar", petNames]]);
623
+
624
+ const resolved = view.update();
625
+ expect(resolved.asset.values).toHaveLength(4);
626
+ expect(resolved.asset.values).toMatchSnapshot();
627
+ expect(resolved.asset.values[0].asset.value).toBe("item Ginger");
628
+ expect(resolved.asset.values[1].asset.value).toBe("item Vokey");
629
+
630
+ // Test that dynamic updates maintain the correct order
631
+ const barBinding = parseBinding("foo.bar");
632
+ model.set([[barBinding, ["Louis", "Bob", "Nuri"]]]);
633
+
634
+ const updated = view.update();
635
+ expect(updated.asset.values).toMatchSnapshot();
636
+ expect(updated.asset.values).toHaveLength(5);
637
+ expect(updated.asset.values[0].asset.value).toBe("item Louis");
638
+ expect(updated.asset.values[1].asset.value).toBe("item Bob");
639
+ expect(updated.asset.values[2].asset.value).toBe("item Nuri");
640
+ });
641
+ it("Should support placement for both static and dynamic templates", () => {
642
+ const petNames = ["Ginger", "Vokey"];
643
+ const model = withParser(new LocalModel({}), parseBinding);
644
+ const evaluator = new ExpressionEvaluator({ model });
645
+ const schema = new SchemaController();
646
+
647
+ const view = new ViewInstance(
648
+ {
649
+ id: "my-view",
650
+ asset: {
651
+ id: "foo",
652
+ type: "collection",
653
+ template: [
654
+ {
655
+ dynamic: true,
656
+ data: "foo.bar",
657
+ output: "values",
658
+ placement: "append",
659
+ value: {
660
+ asset: {
661
+ id: "dynamic-_index_",
662
+ type: "text",
663
+ value: "dynamic {{foo.bar._index_}}",
664
+ },
665
+ },
666
+ },
667
+ {
668
+ dynamic: false,
669
+ data: "foo.bar",
670
+ output: "values",
671
+ placement: "prepend",
672
+ value: {
673
+ asset: {
674
+ id: "static-_index_",
675
+ type: "text",
676
+ value: "static {{foo.bar._index_}}",
677
+ },
678
+ },
679
+ },
680
+ ],
681
+ label: {
682
+ asset: {
683
+ id: "label-test",
684
+ value: "A text label",
685
+ type: "text",
686
+ },
687
+ },
688
+ values: [
689
+ {
690
+ asset: {
691
+ id: "value-1",
692
+ type: "text",
693
+ value: "First value in the collection",
694
+ },
695
+ },
696
+ {
697
+ asset: {
698
+ id: "value-2",
699
+ type: "text",
700
+ value: "Second value in the collection",
701
+ },
702
+ },
703
+ ],
704
+ },
705
+ } as any,
706
+ {
707
+ model,
708
+ parseBinding,
709
+ evaluator,
710
+ schema,
711
+ },
712
+ );
713
+
714
+ const pluginOptions = toNodeResolveOptions(view.resolverOptions);
715
+ new TemplatePlugin(pluginOptions).apply(view);
716
+ new StringResolverPlugin().apply(view);
717
+ new AssetPlugin().apply(view);
718
+ new MultiNodePlugin().apply(view);
719
+
720
+ model.set([["foo.bar", petNames]]);
721
+
722
+ const resolved = view.update();
723
+ expect(resolved.asset.values).toMatchSnapshot();
724
+ expect(resolved.asset.values).toHaveLength(6);
725
+ // Prepend first - static template
726
+ expect(resolved.asset.values[0].asset.value).toBe("static Ginger");
727
+ expect(resolved.asset.values[1].asset.value).toBe("static Vokey");
728
+ // Non-template data
729
+ expect(resolved.asset.values[2].asset.value).toBe(
730
+ "First value in the collection",
731
+ );
732
+ expect(resolved.asset.values[3].asset.value).toBe(
733
+ "Second value in the collection",
734
+ );
735
+ // Append last - dynamic template
736
+ expect(resolved.asset.values[4].asset.value).toBe("dynamic Ginger");
737
+ expect(resolved.asset.values[5].asset.value).toBe("dynamic Vokey");
738
+
739
+ const barBinding = parseBinding("foo.bar");
740
+ model.set([[barBinding, ["Louis", "Bob", "Nuri"]]]);
741
+
742
+ const updated = view.update();
743
+ expect(updated.asset.values).toMatchSnapshot();
744
+ expect(updated.asset.values).toHaveLength(7);
745
+ expect(updated.asset.values[4].asset.value).toBe("dynamic Louis");
746
+ expect(updated.asset.values[5].asset.value).toBe("dynamic Bob");
747
+ expect(updated.asset.values[6].asset.value).toBe("dynamic Nuri");
748
+ });
749
+ });
750
+
751
+ it("Should preserve order when multiple templates have the same placement", () => {
752
+ const model = withParser(new LocalModel({}), parseBinding);
753
+ const evaluator = new ExpressionEvaluator({ model });
754
+ const schema = new SchemaController();
755
+
756
+ const view = new ViewInstance(
757
+ {
758
+ id: "my-view",
759
+ asset: {
760
+ id: "foo",
761
+ type: "collection",
762
+ template: [
763
+ {
764
+ dynamic: true,
765
+ data: "first.data",
766
+ output: "values",
767
+ placement: "append", // Both templates have "append" placement
768
+ value: {
769
+ asset: {
770
+ id: "first-_index_",
771
+ type: "text",
772
+ value: "first {{first.data._index_}}",
773
+ },
774
+ },
775
+ },
776
+ {
777
+ dynamic: true,
778
+ data: "second.data",
779
+ output: "values",
780
+ placement: "append", // Both templates have "append" placement
781
+ value: {
782
+ asset: {
783
+ id: "second-_index_",
784
+ type: "text",
785
+ value: "second {{second.data._index_}}",
786
+ },
787
+ },
788
+ },
789
+ ],
790
+ values: [
791
+ {
792
+ asset: {
793
+ id: "static-value",
794
+ type: "text",
795
+ value: "Static value in the collection",
796
+ },
797
+ },
798
+ ],
799
+ },
800
+ } as any,
801
+ {
802
+ model,
803
+ parseBinding,
804
+ evaluator,
805
+ schema,
806
+ },
807
+ );
808
+
809
+ const pluginOptions = toNodeResolveOptions(view.resolverOptions);
810
+ new TemplatePlugin(pluginOptions).apply(view);
811
+ new StringResolverPlugin().apply(view);
812
+ new AssetPlugin().apply(view);
813
+ new MultiNodePlugin().apply(view);
814
+
815
+ // Set data for both template data sources
816
+ model.set([
817
+ ["first.data", ["A", "B"]],
818
+ ["second.data", ["C", "D"]],
819
+ ]);
820
+
821
+ const resolved = view.update();
822
+
823
+ // We should have 5 values total:
824
+ // 1 static value + 2 values from first.data + 2 values from second.data
825
+ expect(resolved.asset.values).toHaveLength(5);
826
+
827
+ // Static value should be first
828
+ expect(resolved.asset.values[0].asset.id).toBe("static-value");
829
+ expect(resolved.asset.values[0].asset.value).toBe(
830
+ "Static value in the collection",
831
+ );
832
+
833
+ // Then first.data template items (in their original order)
834
+ expect(resolved.asset.values[1].asset.id).toBe("first-0");
835
+ expect(resolved.asset.values[1].asset.value).toBe("first A");
836
+ expect(resolved.asset.values[2].asset.id).toBe("first-1");
837
+ expect(resolved.asset.values[2].asset.value).toBe("first B");
838
+
839
+ // Then second.data template items
840
+ expect(resolved.asset.values[3].asset.id).toBe("second-0");
841
+ expect(resolved.asset.values[3].asset.value).toBe("second C");
842
+ expect(resolved.asset.values[4].asset.id).toBe("second-1");
843
+ expect(resolved.asset.values[4].asset.value).toBe("second D");
844
+
845
+ // Make sure the ordering is preserved in the snapshot
846
+ expect(resolved.asset.values).toMatchSnapshot();
383
847
  });
384
848
  });