@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.
- package/dist/Player.native.js +61 -1
- package/dist/Player.native.js.map +1 -1
- package/dist/cjs/index.cjs +61 -1
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/index.legacy-esm.js +61 -1
- package/dist/index.mjs +61 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/view/builder/index.test.ts +29 -1
- package/src/view/builder/index.ts +29 -1
- package/src/view/parser/types.ts +4 -1
- package/src/view/plugins/__tests__/__snapshots__/template.test.ts.snap +328 -48
- package/src/view/plugins/__tests__/template.test.ts +592 -128
- package/src/view/plugins/template.ts +67 -4
- package/types/view/builder/index.d.ts +8 -0
- package/types/view/parser/types.d.ts +3 -1
- package/types/view/plugins/template.d.ts +4 -1
|
@@ -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("
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
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
|
-
|
|
237
|
+
const pluginOptions = toNodeResolveOptions(view.resolverOptions);
|
|
238
|
+
new TemplatePlugin(pluginOptions).apply(view);
|
|
239
|
+
new StringResolverPlugin().apply(view);
|
|
230
240
|
|
|
231
|
-
|
|
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
|
-
|
|
243
|
+
const resolved = view.update();
|
|
241
244
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
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
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|
-
|
|
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
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|
-
|
|
288
|
-
{
|
|
289
|
-
model,
|
|
290
|
-
parseBinding,
|
|
291
|
-
evaluator,
|
|
292
|
-
schema,
|
|
293
|
-
},
|
|
294
|
-
);
|
|
307
|
+
);
|
|
295
308
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
309
|
+
const pluginOptions = toNodeResolveOptions(view.resolverOptions);
|
|
310
|
+
new TemplatePlugin(pluginOptions).apply(view);
|
|
311
|
+
new StringResolverPlugin().apply(view);
|
|
299
312
|
|
|
300
|
-
|
|
313
|
+
model.set([["foo.bar", petNames]]);
|
|
301
314
|
|
|
302
|
-
|
|
315
|
+
const resolved = view.update();
|
|
303
316
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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
|
-
|
|
314
|
-
|
|
326
|
+
const barBinding = parseBinding("foo.bar");
|
|
327
|
+
model.set([[barBinding, ["Vokey", "Louis", "Bob"]]]);
|
|
315
328
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
});
|