@cocreate/render 1.51.0 → 1.53.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +1 -1
- package/package.json +6 -6
- package/src/index.js +334 -179
- package/test/render-order.test.cjs +90 -0
package/CONTRIBUTING.md
CHANGED
|
@@ -13,7 +13,7 @@ Fork the [project on Github](https://github.com/CoCreate-app/CoCreate-render) an
|
|
|
13
13
|
```
|
|
14
14
|
git render https://github.com/contributor/CoCreate-render.git
|
|
15
15
|
cd CoCreate-render
|
|
16
|
-
git remote add upstream https://
|
|
16
|
+
git remote add upstream https://gitlab.com/cocreatejs/CoCreate-render.git
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
## Create a Topic Branch
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cocreate/render",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.53.0",
|
|
4
4
|
"description": "A simple render component in vanilla javascript. Easily configured using HTML5 attributes and/or JavaScript API.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"render",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"repository": {
|
|
24
24
|
"type": "git",
|
|
25
|
-
"url": "git+https://
|
|
25
|
+
"url": "git+https://gitlab.com/cocreatejs/CoCreate-render.git"
|
|
26
26
|
},
|
|
27
27
|
"author": "CoCreate LLC",
|
|
28
28
|
"license": "AGPL-3.0",
|
|
@@ -37,13 +37,13 @@
|
|
|
37
37
|
"main": "./src/index.js",
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@cocreate/actions": "^1.24.0",
|
|
40
|
-
"@cocreate/api": "^1.
|
|
40
|
+
"@cocreate/api": "^1.31.0",
|
|
41
41
|
"@cocreate/element-prototype": "^1.35.0",
|
|
42
|
-
"@cocreate/observer": "^1.
|
|
43
|
-
"@cocreate/utils": "^1.
|
|
42
|
+
"@cocreate/observer": "^1.23.0",
|
|
43
|
+
"@cocreate/utils": "^1.54.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@cocreate/webpack": "^1.
|
|
46
|
+
"@cocreate/webpack": "^1.9.0"
|
|
47
47
|
},
|
|
48
48
|
"allowScripts": {
|
|
49
49
|
"@cocreate/actions@1.21.4": true,
|
package/src/index.js
CHANGED
|
@@ -34,9 +34,51 @@ import {
|
|
|
34
34
|
import "@cocreate/element-prototype";
|
|
35
35
|
import "./index.css";
|
|
36
36
|
|
|
37
|
-
const sources = new Map();
|
|
38
|
-
const renderedNodes = new Map();
|
|
39
|
-
const elementSelector = "[render-query]";
|
|
37
|
+
const sources = new Map();
|
|
38
|
+
const renderedNodes = new Map();
|
|
39
|
+
const elementSelector = "[render-query]";
|
|
40
|
+
|
|
41
|
+
function getRenderContext(renderedNode, key) {
|
|
42
|
+
const keys = renderedNode?.keys;
|
|
43
|
+
if (!keys || Array.isArray(keys) || typeof keys !== "object") return;
|
|
44
|
+
|
|
45
|
+
const contextKey = key || Object.keys(keys).at(-1);
|
|
46
|
+
if (!contextKey || !keys[contextKey]) return;
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
key: contextKey,
|
|
50
|
+
keyPath: keys[contextKey].path,
|
|
51
|
+
renderData: keys[contextKey].data,
|
|
52
|
+
renderAs: keys[contextKey].renderAs
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function setRenderContext(renderedNode, context = {}) {
|
|
57
|
+
if (!renderedNode.keys || Array.isArray(renderedNode.keys))
|
|
58
|
+
renderedNode.keys = {};
|
|
59
|
+
|
|
60
|
+
const contextKey =
|
|
61
|
+
context.key || Object.keys(renderedNode.keys).at(-1) || "$default";
|
|
62
|
+
const renderContext = renderedNode.keys[contextKey] || {};
|
|
63
|
+
if (context.keyPath !== undefined)
|
|
64
|
+
renderContext.path = context.keyPath;
|
|
65
|
+
if (context.renderData !== undefined)
|
|
66
|
+
renderContext.data = context.renderData;
|
|
67
|
+
if (context.renderAs !== undefined)
|
|
68
|
+
renderContext.renderAs = context.renderAs;
|
|
69
|
+
|
|
70
|
+
// Reinsert the context so the last key always represents the active
|
|
71
|
+
// render scope without requiring top-level key metadata.
|
|
72
|
+
delete renderedNode.keys[contextKey];
|
|
73
|
+
renderedNode.keys[contextKey] = renderContext;
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
key: contextKey,
|
|
77
|
+
keyPath: renderContext.path,
|
|
78
|
+
renderData: renderContext.data,
|
|
79
|
+
renderAs: renderContext.renderAs
|
|
80
|
+
};
|
|
81
|
+
}
|
|
40
82
|
|
|
41
83
|
/**
|
|
42
84
|
* Initializes elements based on specified rendering attributes. If a specific element is provided, it initializes that element.
|
|
@@ -82,7 +124,7 @@ function init(element) {
|
|
|
82
124
|
} catch (error) {
|
|
83
125
|
console.error("Failed to parse render-data:", error);
|
|
84
126
|
}
|
|
85
|
-
}
|
|
127
|
+
}
|
|
86
128
|
}
|
|
87
129
|
}
|
|
88
130
|
|
|
@@ -163,13 +205,19 @@ async function render({
|
|
|
163
205
|
}
|
|
164
206
|
}
|
|
165
207
|
|
|
166
|
-
if (data
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
208
|
+
if (data.filter) {
|
|
209
|
+
update ??= data.filter.update;
|
|
210
|
+
remove ??= data.filter.remove;
|
|
211
|
+
|
|
212
|
+
const isMutation = remove || update || data.filter.create;
|
|
213
|
+
if (index == null) {
|
|
214
|
+
index = isMutation
|
|
215
|
+
? data.filter.index
|
|
216
|
+
: data.filter.startingIndex ?? data.filter.index;
|
|
217
|
+
}
|
|
170
218
|
}
|
|
171
219
|
|
|
172
|
-
let isMutation = remove || update || (data
|
|
220
|
+
let isMutation = remove || update || (data.filter && data.filter.create);
|
|
173
221
|
|
|
174
222
|
let sourceData = sources.get(source);
|
|
175
223
|
if (!sourceData) {
|
|
@@ -232,12 +280,15 @@ async function render({
|
|
|
232
280
|
|
|
233
281
|
let clone;
|
|
234
282
|
if (remove) {
|
|
235
|
-
|
|
283
|
+
const removedItems = data[key];
|
|
284
|
+
if (!Array.isArray(removedItems) || !clones) continue;
|
|
285
|
+
|
|
286
|
+
for (let j = 0; j < removedItems.length; j++) {
|
|
236
287
|
let cloneKey;
|
|
237
288
|
if (key === "object") {
|
|
238
|
-
cloneKey =
|
|
289
|
+
cloneKey = removedItems[j]._id;
|
|
239
290
|
} else {
|
|
240
|
-
cloneKey =
|
|
291
|
+
cloneKey = removedItems[j].name;
|
|
241
292
|
}
|
|
242
293
|
|
|
243
294
|
clone = clones.get(cloneKey);
|
|
@@ -251,7 +302,7 @@ async function render({
|
|
|
251
302
|
(key && (Array.isArray(data[key]) || renderType)) ||
|
|
252
303
|
Array.isArray(data)
|
|
253
304
|
) {
|
|
254
|
-
if (update) {
|
|
305
|
+
if (update && renderType !== "object") {
|
|
255
306
|
for (let j = 0; j < data[key].length; j++) {
|
|
256
307
|
if (key === "object") {
|
|
257
308
|
clone = clones.get(data[key][j]._id);
|
|
@@ -259,7 +310,8 @@ async function render({
|
|
|
259
310
|
clone = clones.get(data[key][j].name);
|
|
260
311
|
}
|
|
261
312
|
|
|
262
|
-
if (
|
|
313
|
+
if (currentIndex == null)
|
|
314
|
+
currentIndex = data.filter.currentIndex;
|
|
263
315
|
|
|
264
316
|
if (!clone) continue;
|
|
265
317
|
|
|
@@ -270,7 +322,7 @@ async function render({
|
|
|
270
322
|
} else {
|
|
271
323
|
// TODO: if $auto here every subsequent clone will have same value, not replacing here will apply unique value to each clone
|
|
272
324
|
if (key === "$auto")
|
|
273
|
-
key = key.replace(/\$auto/g,
|
|
325
|
+
key = key.replace(/\$auto/g, uuid(6));
|
|
274
326
|
|
|
275
327
|
if (key) element[i].setAttribute("render", key);
|
|
276
328
|
|
|
@@ -289,15 +341,25 @@ async function render({
|
|
|
289
341
|
}
|
|
290
342
|
|
|
291
343
|
async function renderTemplate(template, data, key, index, keyPath) {
|
|
292
|
-
if (
|
|
293
|
-
|
|
344
|
+
if (template && template.nodeType !== 1) {
|
|
345
|
+
template = template.ownerElement || template.parentElement;
|
|
294
346
|
}
|
|
295
|
-
|
|
296
|
-
if (!
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
347
|
+
if (!template) return;
|
|
348
|
+
if (template instanceof HTMLTemplateElement && !template.content.firstElementChild) return;
|
|
349
|
+
let templateData = renderedNodes.get(template);
|
|
350
|
+
if (!templateData) {
|
|
351
|
+
templateData = {
|
|
352
|
+
element: template,
|
|
353
|
+
clones: new Map(),
|
|
354
|
+
isTemplate: true
|
|
355
|
+
};
|
|
356
|
+
renderedNodes.set(template, templateData);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
templateData.element = template;
|
|
360
|
+
if (!templateData.clones) templateData.clones = new Map();
|
|
361
|
+
templateData.isTemplate = true;
|
|
362
|
+
|
|
301
363
|
if (!templateData.parent && !templateData.source) {
|
|
302
364
|
let parentRender = template.parentElement.closest("[render]")
|
|
303
365
|
if (parentRender || template.source)
|
|
@@ -306,65 +368,60 @@ async function renderTemplate(template, data, key, index, keyPath) {
|
|
|
306
368
|
console.log("template parent missing is there no parent or is this an error? and source is", templateData.source)
|
|
307
369
|
}
|
|
308
370
|
|
|
309
|
-
if(!templateData.source && templateData.parent && templateData.parent.source){
|
|
371
|
+
if (!templateData.source && templateData.parent && templateData.parent.source) {
|
|
310
372
|
templateData.source = templateData.parent.source
|
|
311
373
|
}
|
|
312
374
|
|
|
313
|
-
|
|
314
|
-
if (keyPath) {
|
|
315
|
-
templateData.keyPath = keyPath;
|
|
316
|
-
} else if (templateData.parent && templateData.parent.keyPath) {
|
|
317
|
-
templateData.keyPath = templateData.parent.keyPath;
|
|
318
|
-
} else if (key) {
|
|
319
|
-
templateData.keyPath = key;
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
let renderData;
|
|
375
|
+
let renderData;
|
|
324
376
|
if (key) {
|
|
325
377
|
renderData = getRenderValue(templateData.element, data, key);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
if (!renderData) {
|
|
329
|
-
renderData = data;
|
|
330
378
|
}
|
|
331
379
|
|
|
332
|
-
|
|
333
|
-
|
|
380
|
+
if (renderData === undefined || renderData === null) {
|
|
381
|
+
renderData = key ? [] : data;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
let el = templateData.element;
|
|
334
385
|
if (el.nodeType !== 1) {
|
|
335
|
-
|
|
386
|
+
el = templateData.parent.element;
|
|
336
387
|
}
|
|
337
388
|
|
|
338
|
-
let renderAs =
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
389
|
+
let renderAs =
|
|
390
|
+
el.getAttribute("render-as") ||
|
|
391
|
+
el.getAttribute("render") ||
|
|
392
|
+
getRenderContext(templateData, key)?.renderAs ||
|
|
393
|
+
key;
|
|
394
|
+
const parentContext = getRenderContext(templateData.parent);
|
|
395
|
+
const resolvedKeyPath =
|
|
396
|
+
keyPath || key || parentContext?.keyPath;
|
|
397
|
+
const templateContext = setRenderContext(templateData, {
|
|
398
|
+
key,
|
|
399
|
+
keyPath: resolvedKeyPath,
|
|
400
|
+
renderData,
|
|
401
|
+
renderAs
|
|
402
|
+
});
|
|
346
403
|
|
|
347
404
|
let renderType;
|
|
348
405
|
if (templateData.source && templateData.source.element)
|
|
349
406
|
renderType = templateData.source.element.getAttribute("render-type");
|
|
350
407
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
templateData.source.element.getAttribute("render-reverse");
|
|
355
|
-
if (renderReverse === null)
|
|
356
|
-
renderReverse = el.getAttribute("render-reverse");
|
|
357
|
-
}
|
|
408
|
+
const renderReverse =
|
|
409
|
+
templateData.source?.element?.getAttribute("render-reverse") ??
|
|
410
|
+
el.getAttribute("render-reverse");
|
|
358
411
|
|
|
359
412
|
let exclude = el.getAttribute("render-exclude") || "";
|
|
360
413
|
if (exclude) {
|
|
361
414
|
exclude = exclude.replace(/ /g, "").split(",");
|
|
362
415
|
}
|
|
416
|
+
const include = el.getAttribute("render-include");
|
|
417
|
+
const includePattern = include ? new RegExp(include) : null;
|
|
363
418
|
let reference = el.getAttribute("render-reference");
|
|
364
419
|
|
|
365
|
-
|
|
420
|
+
const isLoadMore = data.filter?.loadMore && !data.filter?.overwrite;
|
|
421
|
+
const appendIndex = templateData.clones.size;
|
|
422
|
+
let isInsert = data.filter && (data.filter.create || data.filter.update);
|
|
366
423
|
if (renderType !== "object") {
|
|
367
|
-
if ((!isInsert && !index) || data
|
|
424
|
+
if ((!isInsert && !isLoadMore && !index) || data.filter?.overwrite) {
|
|
368
425
|
if (!templateData.clones) {
|
|
369
426
|
return
|
|
370
427
|
if (templateData.template) {
|
|
@@ -379,7 +436,7 @@ async function renderTemplate(template, data, key, index, keyPath) {
|
|
|
379
436
|
element.remove();
|
|
380
437
|
templateData.clones.delete(key);
|
|
381
438
|
}
|
|
382
|
-
}
|
|
439
|
+
}
|
|
383
440
|
}
|
|
384
441
|
|
|
385
442
|
if ((key && !Array.isArray(renderData)) || renderType === "object") {
|
|
@@ -387,19 +444,35 @@ async function renderTemplate(template, data, key, index, keyPath) {
|
|
|
387
444
|
renderData = renderData[0];
|
|
388
445
|
|
|
389
446
|
const keys = Object.keys(renderData);
|
|
447
|
+
if (renderType === "object" && data.method?.endsWith(".read")) {
|
|
448
|
+
const present = new Set(keys);
|
|
449
|
+
for (const [id, element] of templateData.clones) {
|
|
450
|
+
if (present.has(element.getAttribute("renderedKey"))) continue;
|
|
451
|
+
renderedNodes.delete(element);
|
|
452
|
+
element.remove();
|
|
453
|
+
templateData.clones.delete(id);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
const fieldClones = new Map(
|
|
457
|
+
Array.from(templateData.clones.values(), (element) => [
|
|
458
|
+
element.getAttribute("renderedKey"),
|
|
459
|
+
element
|
|
460
|
+
])
|
|
461
|
+
);
|
|
390
462
|
for (let i = 0; i < keys.length; i++) {
|
|
391
|
-
let clone =
|
|
463
|
+
let clone = fieldClones.get(keys[i]);
|
|
392
464
|
clone = renderedNodes.get(clone);
|
|
393
465
|
|
|
394
466
|
if (
|
|
395
467
|
exclude.includes(keys[i]) ||
|
|
468
|
+
(includePattern && !includePattern.test(keys[i])) ||
|
|
396
469
|
(reference === "false" &&
|
|
397
470
|
["$storage", "$database", "$array"].includes(key))
|
|
398
471
|
) {
|
|
399
472
|
if (clone) {
|
|
400
473
|
renderedNodes.delete(clone.element);
|
|
401
474
|
clone.element.remove();
|
|
402
|
-
templateData.clones.delete(
|
|
475
|
+
templateData.clones.delete(clone.element.getAttribute("eid"));
|
|
403
476
|
}
|
|
404
477
|
continue;
|
|
405
478
|
}
|
|
@@ -410,7 +483,11 @@ async function renderTemplate(template, data, key, index, keyPath) {
|
|
|
410
483
|
if (Array.isArray(value)) type = "array";
|
|
411
484
|
else if (typeof value == "object") type = "object";
|
|
412
485
|
|
|
413
|
-
|
|
486
|
+
// Nested object templates still need fields from the source record.
|
|
487
|
+
let Data = {
|
|
488
|
+
...data,
|
|
489
|
+
[renderAs]: { key: keys[i], value, type }
|
|
490
|
+
};
|
|
414
491
|
|
|
415
492
|
if (!clone) {
|
|
416
493
|
clone = cloneTemplate(templateData);
|
|
@@ -424,13 +501,19 @@ async function renderTemplate(template, data, key, index, keyPath) {
|
|
|
424
501
|
let renderedKey = key.split(".");
|
|
425
502
|
renderedKey = renderedKey[renderedKey.length - 1];
|
|
426
503
|
|
|
427
|
-
|
|
428
|
-
clone.
|
|
429
|
-
clone.
|
|
430
|
-
clone
|
|
504
|
+
const cloneKeyPath = `${templateContext.keyPath}.${renderedKey}`;
|
|
505
|
+
clone.parentKey = renderedKey;
|
|
506
|
+
clone.renderKey = key;
|
|
507
|
+
setRenderContext(clone, {
|
|
508
|
+
key: keys[i],
|
|
509
|
+
keyPath: cloneKeyPath,
|
|
510
|
+
renderData: Data,
|
|
511
|
+
renderAs
|
|
512
|
+
});
|
|
431
513
|
clone.element.setAttribute("renderedKey", keys[i]);
|
|
432
514
|
await renderValues(clone.element, Data, keys[i], renderAs);
|
|
433
515
|
insertElement(templateData, clone.element, index);
|
|
516
|
+
fieldClones.set(keys[i], clone.element);
|
|
434
517
|
}
|
|
435
518
|
} else {
|
|
436
519
|
if (!key && !Array.isArray(renderData)) {
|
|
@@ -441,7 +524,12 @@ async function renderTemplate(template, data, key, index, keyPath) {
|
|
|
441
524
|
|
|
442
525
|
if (!renderData) {
|
|
443
526
|
let clone = cloneTemplate(templateData);
|
|
444
|
-
clone
|
|
527
|
+
setRenderContext(clone, {
|
|
528
|
+
key,
|
|
529
|
+
keyPath: templateContext.keyPath,
|
|
530
|
+
renderData: data,
|
|
531
|
+
renderAs
|
|
532
|
+
});
|
|
445
533
|
await renderValues(clone.element, data, key, renderAs);
|
|
446
534
|
insertElement(templateData, clone.element, index);
|
|
447
535
|
} else {
|
|
@@ -453,7 +541,7 @@ async function renderTemplate(template, data, key, index, keyPath) {
|
|
|
453
541
|
let test = clone.element.getAttribute("render-as");
|
|
454
542
|
if (test) renderAs = test;
|
|
455
543
|
|
|
456
|
-
|
|
544
|
+
const cloneKeyPath = `${templateContext.keyPath || ""}[${i}]`;
|
|
457
545
|
|
|
458
546
|
let object;
|
|
459
547
|
if (
|
|
@@ -480,11 +568,22 @@ async function renderTemplate(template, data, key, index, keyPath) {
|
|
|
480
568
|
object = dotNotationToObject(object);
|
|
481
569
|
} else object = object;
|
|
482
570
|
|
|
483
|
-
let
|
|
484
|
-
if (key !== "data")
|
|
571
|
+
let insertIndex;
|
|
572
|
+
if (key !== "data") {
|
|
573
|
+
insertIndex = isLoadMore
|
|
574
|
+
? appendIndex + i
|
|
575
|
+
: data.filter?.create && index != null
|
|
576
|
+
? index + i
|
|
577
|
+
: i;
|
|
578
|
+
}
|
|
485
579
|
|
|
486
|
-
clone
|
|
487
|
-
|
|
580
|
+
setRenderContext(clone, {
|
|
581
|
+
key,
|
|
582
|
+
keyPath: cloneKeyPath,
|
|
583
|
+
renderData: object,
|
|
584
|
+
renderAs
|
|
585
|
+
});
|
|
586
|
+
clone.index = insertIndex
|
|
488
587
|
await renderValues(
|
|
489
588
|
clone.element,
|
|
490
589
|
object,
|
|
@@ -492,12 +591,12 @@ async function renderTemplate(template, data, key, index, keyPath) {
|
|
|
492
591
|
renderAs,
|
|
493
592
|
null,
|
|
494
593
|
null,
|
|
495
|
-
|
|
594
|
+
insertIndex
|
|
496
595
|
);
|
|
497
596
|
insertElement(
|
|
498
597
|
templateData,
|
|
499
598
|
clone.element,
|
|
500
|
-
|
|
599
|
+
insertIndex,
|
|
501
600
|
"",
|
|
502
601
|
data,
|
|
503
602
|
renderReverse
|
|
@@ -507,17 +606,17 @@ async function renderTemplate(template, data, key, index, keyPath) {
|
|
|
507
606
|
}
|
|
508
607
|
}
|
|
509
608
|
|
|
510
|
-
function cloneTemplate(template) {
|
|
609
|
+
function cloneTemplate(template) {
|
|
511
610
|
let clone = template.element.cloneNode(true);
|
|
512
611
|
let cloneTagName = clone.tagName;
|
|
513
612
|
|
|
514
613
|
if (clone.tagName == "TEMPLATE") {
|
|
515
|
-
clone =
|
|
614
|
+
clone = clone.content.firstElementChild;
|
|
516
615
|
for (let attribute of template.element.attributes) {
|
|
517
616
|
clone.setAttribute(attribute.name, attribute.value || "");
|
|
518
617
|
}
|
|
519
618
|
} else {
|
|
520
|
-
clone.classList
|
|
619
|
+
clone.classList?.remove("template");
|
|
521
620
|
clone.removeAttribute("template");
|
|
522
621
|
}
|
|
523
622
|
|
|
@@ -525,7 +624,7 @@ function cloneTemplate(template) {
|
|
|
525
624
|
|
|
526
625
|
let renderAs = clone.getAttribute("render-as");
|
|
527
626
|
if (renderAs === "$auto") {
|
|
528
|
-
renderAs =
|
|
627
|
+
renderAs = uuid(6);
|
|
529
628
|
clone = clone.outerHTML.replace(/\$auto/g, renderAs);
|
|
530
629
|
}
|
|
531
630
|
|
|
@@ -542,10 +641,27 @@ function cloneTemplate(template) {
|
|
|
542
641
|
|
|
543
642
|
let renderedNode = { template, element: clone };
|
|
544
643
|
renderedNodes.set(clone, renderedNode);
|
|
545
|
-
return renderedNode;
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
function
|
|
644
|
+
return renderedNode;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function getCloneIdentity(data, renderAs) {
|
|
648
|
+
const value = renderAs ? getValueFromObject(data, renderAs) : data;
|
|
649
|
+
if (!value || typeof value !== "object") return;
|
|
650
|
+
|
|
651
|
+
for (const key of ["_id", "id", "object", "name", "key"]) {
|
|
652
|
+
const identity = value[key];
|
|
653
|
+
if (
|
|
654
|
+
(typeof identity === "string" || typeof identity === "number") &&
|
|
655
|
+
identity !== "" &&
|
|
656
|
+
identity !== "{}" &&
|
|
657
|
+
identity !== "[]"
|
|
658
|
+
) {
|
|
659
|
+
return String(identity);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
function insertElement(
|
|
549
665
|
template,
|
|
550
666
|
element,
|
|
551
667
|
index,
|
|
@@ -555,7 +671,7 @@ function insertElement(
|
|
|
555
671
|
) {
|
|
556
672
|
let eid = element.getAttribute("eid");
|
|
557
673
|
if (!eid) {
|
|
558
|
-
eid =
|
|
674
|
+
eid = uuid(6);
|
|
559
675
|
element.setAttribute("eid", eid);
|
|
560
676
|
}
|
|
561
677
|
|
|
@@ -574,11 +690,11 @@ function insertElement(
|
|
|
574
690
|
item = clones.splice(currentIndex, 1)[0];
|
|
575
691
|
}
|
|
576
692
|
|
|
577
|
-
if (data && data
|
|
578
|
-
index += data
|
|
693
|
+
if (data && data.filter && data.filter.startingIndex && !data.filter.loadMore)
|
|
694
|
+
index += data.filter.startingIndex;
|
|
579
695
|
clones.splice(index, 0, item); // Insert item into clones at the specified index
|
|
580
696
|
|
|
581
|
-
if (renderReverse
|
|
697
|
+
if (renderReverse != null && renderReverse !== false && renderReverse !== "false") {
|
|
582
698
|
// Chat-specific logic: reverse rendering but still inserting based on sorting
|
|
583
699
|
if (clones[index + 1] && clones[index + 1][1] !== element) {
|
|
584
700
|
clones[index + 1][1].insertAdjacentElement("afterend", element);
|
|
@@ -612,50 +728,47 @@ async function renderValues(node, data, key, renderAs, keyPath, parent, index) {
|
|
|
612
728
|
if (!data) return;
|
|
613
729
|
|
|
614
730
|
let renderedNode = renderedNodes.get(node);
|
|
615
|
-
if (!renderedNode)
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
if (keyPath)
|
|
624
|
-
|
|
731
|
+
if (!renderedNode) {
|
|
732
|
+
renderedNode = { element: node };
|
|
733
|
+
renderedNodes.set(node, renderedNode);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
const previousContext = getRenderContext(renderedNode, key);
|
|
737
|
+
if (!key) key = previousContext?.key;
|
|
738
|
+
if (!renderAs) renderAs = previousContext?.renderAs;
|
|
739
|
+
if (!keyPath) keyPath = previousContext?.keyPath;
|
|
740
|
+
setRenderContext(renderedNode, {
|
|
741
|
+
key,
|
|
742
|
+
keyPath,
|
|
743
|
+
renderData: data,
|
|
744
|
+
renderAs
|
|
745
|
+
});
|
|
625
746
|
|
|
626
747
|
if (parent) renderedNode.parent = parent;
|
|
627
748
|
|
|
628
|
-
if (node.nodeType == 1) {
|
|
629
|
-
if (node.hasAttribute("render-clone")) {
|
|
630
|
-
parent = renderedNode;
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
console.log("temp could not be found");
|
|
642
|
-
} else if (!temp.clones) {
|
|
643
|
-
if (temp.template) temp = temp.template;
|
|
644
|
-
else console.log(temp);
|
|
645
|
-
|
|
646
|
-
if (oldEid && oldEid !== eid) temp.clones.delete(oldEid);
|
|
647
|
-
|
|
749
|
+
if (node.nodeType == 1) {
|
|
750
|
+
if (node.hasAttribute("render-clone")) {
|
|
751
|
+
parent = renderedNode;
|
|
752
|
+
|
|
753
|
+
const eid = getCloneIdentity(data, renderAs);
|
|
754
|
+
if (eid) {
|
|
755
|
+
let oldEid = renderedNode.eid;
|
|
756
|
+
let temp = renderedNode.template;
|
|
757
|
+
while (temp && !temp.clones)
|
|
758
|
+
temp = temp.template || temp.parent;
|
|
759
|
+
|
|
760
|
+
if (temp?.clones && oldEid && oldEid !== eid) {
|
|
761
|
+
temp.clones.delete(oldEid);
|
|
648
762
|
temp.clones.set(eid, node);
|
|
649
763
|
}
|
|
650
764
|
|
|
651
765
|
renderedNode.eid = eid;
|
|
766
|
+
|
|
767
|
+
node.setAttribute("eid", eid);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
652
770
|
|
|
653
|
-
|
|
654
|
-
break;
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
for (let attr of node.attributes) {
|
|
771
|
+
for (let attr of Array.from(node.attributes)) {
|
|
659
772
|
let name = attr.name;
|
|
660
773
|
let value = attr.value;
|
|
661
774
|
|
|
@@ -667,6 +780,7 @@ async function renderValues(node, data, key, renderAs, keyPath, parent, index) {
|
|
|
667
780
|
renderAs,
|
|
668
781
|
parent: renderedNode
|
|
669
782
|
};
|
|
783
|
+
renderedNodes.set(attr, renderedAttribute);
|
|
670
784
|
}
|
|
671
785
|
|
|
672
786
|
let namePlaceholder = renderedAttribute.placeholder.name || name;
|
|
@@ -688,15 +802,13 @@ async function renderValues(node, data, key, renderAs, keyPath, parent, index) {
|
|
|
688
802
|
renderedAttribute,
|
|
689
803
|
index
|
|
690
804
|
);
|
|
691
|
-
if (namePlaceholder.includes("{{") && name) {
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
node.setAttribute(attributeName, attributeValue);
|
|
699
|
-
});
|
|
805
|
+
if (namePlaceholder.includes("{{") && name && !name.includes("{{")) {
|
|
806
|
+
if (name !== attr.name) {
|
|
807
|
+
node.removeAttribute(attr.name);
|
|
808
|
+
renderedNodes.delete(attr);
|
|
809
|
+
}
|
|
810
|
+
node.setAttribute(name, value ?? "");
|
|
811
|
+
renderedNodes.set(node.getAttributeNode(name), renderedAttribute);
|
|
700
812
|
} else if (name === undefined || name === null || name === "") {
|
|
701
813
|
renderedNodes.delete(attr);
|
|
702
814
|
node.removeAttribute(attr.name);
|
|
@@ -732,11 +844,22 @@ async function renderValues(node, data, key, renderAs, keyPath, parent, index) {
|
|
|
732
844
|
return;
|
|
733
845
|
}
|
|
734
846
|
}
|
|
847
|
+
|
|
848
|
+
// A nested render-query owns its template and descendants. Parent
|
|
849
|
+
// rendering may resolve this controller's attributes, but its own
|
|
850
|
+
// render cycle must populate its target.
|
|
851
|
+
if (
|
|
852
|
+
node.hasAttribute("render-query") &&
|
|
853
|
+
!node.hasAttribute("render-clone")
|
|
854
|
+
) {
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
|
|
735
858
|
if (node.hasAttribute("render") && !node.hasAttribute("render-clone")) {
|
|
736
859
|
let renderKey = node.getAttribute("render") || key
|
|
737
|
-
renderTemplate(node, data, renderKey);
|
|
738
|
-
} else if (node.childNodes.length > 0) {
|
|
739
|
-
for (let childNode of node.childNodes) {
|
|
860
|
+
await renderTemplate(node, data, renderKey);
|
|
861
|
+
} else if (node.childNodes.length > 0) {
|
|
862
|
+
for (let childNode of Array.from(node.childNodes)) {
|
|
740
863
|
await renderValues(
|
|
741
864
|
childNode,
|
|
742
865
|
data,
|
|
@@ -792,10 +915,12 @@ async function renderValues(node, data, key, renderAs, keyPath, parent, index) {
|
|
|
792
915
|
};
|
|
793
916
|
}
|
|
794
917
|
renderedParent.text = text;
|
|
795
|
-
for (let newChild of newNode.childNodes) {
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
918
|
+
for (let newChild of newNode.childNodes) {
|
|
919
|
+
renderedNodes.set(newChild, {
|
|
920
|
+
...renderedNode,
|
|
921
|
+
element: newChild
|
|
922
|
+
});
|
|
923
|
+
}
|
|
799
924
|
renderedNodes.delete(node);
|
|
800
925
|
node.replaceWith(...newNode.childNodes);
|
|
801
926
|
}
|
|
@@ -856,7 +981,7 @@ async function renderValue(
|
|
|
856
981
|
match = output.match(regex);
|
|
857
982
|
if (match) {
|
|
858
983
|
let value;
|
|
859
|
-
let placeholderContent = match[1].trim();
|
|
984
|
+
let placeholderContent = match[1].trim();
|
|
860
985
|
|
|
861
986
|
try {
|
|
862
987
|
if (placeholderContent === "index" && index >= 0) {
|
|
@@ -894,7 +1019,7 @@ async function renderValue(
|
|
|
894
1019
|
});
|
|
895
1020
|
}
|
|
896
1021
|
}
|
|
897
|
-
|
|
1022
|
+
|
|
898
1023
|
for (let r of replacements) {
|
|
899
1024
|
preProcessed = preProcessed.replace(r.original, r.replacement);
|
|
900
1025
|
}
|
|
@@ -912,7 +1037,8 @@ async function renderValue(
|
|
|
912
1037
|
node,
|
|
913
1038
|
data,
|
|
914
1039
|
preProcessed,
|
|
915
|
-
renderAs
|
|
1040
|
+
renderAs,
|
|
1041
|
+
index
|
|
916
1042
|
);
|
|
917
1043
|
}
|
|
918
1044
|
} catch (error) {
|
|
@@ -948,7 +1074,7 @@ async function renderValue(
|
|
|
948
1074
|
return output;
|
|
949
1075
|
}
|
|
950
1076
|
|
|
951
|
-
function getRenderValue(node, data, key, renderAs) {
|
|
1077
|
+
function getRenderValue(node, data, key, renderAs, renderIndex) {
|
|
952
1078
|
let operator;
|
|
953
1079
|
if (key.startsWith("$")) {
|
|
954
1080
|
let keys = key.split(".");
|
|
@@ -958,20 +1084,37 @@ function getRenderValue(node, data, key, renderAs) {
|
|
|
958
1084
|
}
|
|
959
1085
|
}
|
|
960
1086
|
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
1087
|
+
// Field reads can arrive wrapped under a literal dotted or bracketed key.
|
|
1088
|
+
const hasLiteralKey = Object.prototype.hasOwnProperty.call(data, key);
|
|
1089
|
+
const rootKey = key.split(/[.\[]/, 1)[0];
|
|
1090
|
+
const ownsRootScope =
|
|
1091
|
+
hasLiteralKey || Object.prototype.hasOwnProperty.call(data, rootKey);
|
|
1092
|
+
let value = hasLiteralKey
|
|
1093
|
+
? data[key]
|
|
1094
|
+
: getValueFromObject(data, key);
|
|
1095
|
+
|
|
1096
|
+
// Once the current scope owns a root (for example `object`), a missing
|
|
1097
|
+
// descendant must remain missing instead of leaking in from another
|
|
1098
|
+
// clone's ancestor/source data.
|
|
1099
|
+
if ((value === undefined || value === null) && node && !ownsRootScope) {
|
|
1100
|
+
let parentTemplate = node;
|
|
1101
|
+
do {
|
|
1102
|
+
let parentNode = renderedNodes.get(parentTemplate);
|
|
1103
|
+
if (parentNode) {
|
|
1104
|
+
if (
|
|
1105
|
+
(value === undefined || value === null) &&
|
|
1106
|
+
(parentNode.parent || parentNode.template)
|
|
1107
|
+
) {
|
|
1108
|
+
let Data,
|
|
1109
|
+
eid,
|
|
1110
|
+
parent = parentNode.parent || parentNode.template;
|
|
1111
|
+
do {
|
|
1112
|
+
const parentNodeContext = getRenderContext(parentNode);
|
|
1113
|
+
const parentContext = getRenderContext(parent);
|
|
1114
|
+
if (key.includes("keyPath")) {
|
|
1115
|
+
value =
|
|
1116
|
+
parentNodeContext?.keyPath ||
|
|
1117
|
+
parentContext?.keyPath;
|
|
975
1118
|
} else if (key.includes("parentKey")) {
|
|
976
1119
|
value =
|
|
977
1120
|
parentNode.parentKey ||
|
|
@@ -982,7 +1125,7 @@ function getRenderValue(node, data, key, renderAs) {
|
|
|
982
1125
|
} else if (key === "object" || key === "_id")
|
|
983
1126
|
value = ObjectId().toString();
|
|
984
1127
|
else if (key === "uuid") {
|
|
985
|
-
value =
|
|
1128
|
+
value = uuid(6);
|
|
986
1129
|
} else if (parent.source) {
|
|
987
1130
|
Data = parent.source.data;
|
|
988
1131
|
} else if (parent.parent) {
|
|
@@ -994,28 +1137,36 @@ function getRenderValue(node, data, key, renderAs) {
|
|
|
994
1137
|
}
|
|
995
1138
|
|
|
996
1139
|
if (!Data && parent && parent.eid) eid = parent.eid;
|
|
997
|
-
} while (
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1140
|
+
} while (
|
|
1141
|
+
(value === undefined || value === null) &&
|
|
1142
|
+
parent &&
|
|
1143
|
+
!Data
|
|
1144
|
+
);
|
|
1145
|
+
|
|
1146
|
+
if ((value === undefined || value === null) && Data) {
|
|
1147
|
+
value = getValueFromObject(Data, key);
|
|
1148
|
+
}
|
|
1149
|
+
if (
|
|
1150
|
+
(value === undefined || value === null) &&
|
|
1151
|
+
key.includes(".")
|
|
1152
|
+
) {
|
|
1003
1153
|
let renderedData = getValueFromObject(
|
|
1004
1154
|
Data,
|
|
1005
1155
|
key.split(".")[0]
|
|
1006
1156
|
);
|
|
1007
|
-
if (renderedData) {
|
|
1008
|
-
let index;
|
|
1157
|
+
if (renderedData !== undefined && renderedData !== null) {
|
|
1158
|
+
let index = renderIndex;
|
|
1009
1159
|
if (
|
|
1010
|
-
|
|
1011
|
-
parent.clones.size
|
|
1160
|
+
index === undefined &&
|
|
1161
|
+
(!parent.clones.size ||
|
|
1162
|
+
parent.clones.size === 1)
|
|
1012
1163
|
) {
|
|
1013
1164
|
index = 0;
|
|
1014
|
-
} else if (eid) {
|
|
1165
|
+
} else if (index === undefined && eid) {
|
|
1015
1166
|
index = Array.from(
|
|
1016
1167
|
parent.clones.keys()
|
|
1017
1168
|
).indexOf(eid);
|
|
1018
|
-
}
|
|
1169
|
+
}
|
|
1019
1170
|
|
|
1020
1171
|
if (index >= 0) {
|
|
1021
1172
|
Data = {
|
|
@@ -1028,13 +1179,16 @@ function getRenderValue(node, data, key, renderAs) {
|
|
|
1028
1179
|
}
|
|
1029
1180
|
}
|
|
1030
1181
|
|
|
1031
|
-
if (
|
|
1182
|
+
if (
|
|
1183
|
+
(value === undefined || value === null) &&
|
|
1184
|
+
parentTemplate.parentElement
|
|
1185
|
+
) {
|
|
1032
1186
|
parentTemplate =
|
|
1033
1187
|
parentTemplate.parentElement.closest("[render]");
|
|
1034
1188
|
} else {
|
|
1035
1189
|
parentTemplate = undefined;
|
|
1036
1190
|
}
|
|
1037
|
-
} while (
|
|
1191
|
+
} while ((value === undefined || value === null) && parentTemplate);
|
|
1038
1192
|
}
|
|
1039
1193
|
|
|
1040
1194
|
if (operator) {
|
|
@@ -1150,12 +1304,13 @@ Observer.init({
|
|
|
1150
1304
|
let parentElement = mutation.target.parentElement.closest("[render]");
|
|
1151
1305
|
if (!parentElement) return;
|
|
1152
1306
|
|
|
1153
|
-
let renderedNode = renderedNodes.get(parentElement);
|
|
1154
|
-
let data;
|
|
1155
|
-
if (renderedNode) {
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1307
|
+
let renderedNode = renderedNodes.get(parentElement);
|
|
1308
|
+
let data;
|
|
1309
|
+
if (renderedNode) {
|
|
1310
|
+
const renderContext = getRenderContext(renderedNode);
|
|
1311
|
+
if (renderContext?.renderData) {
|
|
1312
|
+
data = renderContext.renderData;
|
|
1313
|
+
}
|
|
1159
1314
|
} else {
|
|
1160
1315
|
let sourceElement = mutation.target.parentElement.closest("[render-query]")
|
|
1161
1316
|
let sourceData = sources.get(sourceElement);
|
|
@@ -1184,4 +1339,4 @@ Observer.init({
|
|
|
1184
1339
|
|
|
1185
1340
|
init();
|
|
1186
1341
|
|
|
1187
|
-
export { render, renderValue, sources, renderedNodes };
|
|
1342
|
+
export { render, renderValue, sources, renderedNodes };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
const assert = require('node:assert/strict');
|
|
2
|
+
const { readFileSync } = require('node:fs');
|
|
3
|
+
const { join } = require('node:path');
|
|
4
|
+
const test = require('node:test');
|
|
5
|
+
const vm = require('node:vm');
|
|
6
|
+
|
|
7
|
+
const source = readFileSync(join(__dirname, '../src/index.js'), 'utf8');
|
|
8
|
+
const renderTemplate = source.slice(source.indexOf('async function renderTemplate('), source.indexOf('function cloneTemplate('));
|
|
9
|
+
const insertElement = source.slice(source.indexOf('function insertElement('), source.indexOf('async function renderValues('));
|
|
10
|
+
const regions = ['us-east-1', 'us-east-2', 'us-west-1', 'me-south-1'];
|
|
11
|
+
|
|
12
|
+
async function renderOrder({ templateReverse = null, sourceReverse, withSource = false } = {}) {
|
|
13
|
+
const siblings = [];
|
|
14
|
+
let nextId = 0;
|
|
15
|
+
|
|
16
|
+
class Element {
|
|
17
|
+
constructor(reverse = null) {
|
|
18
|
+
this.nodeType = 1;
|
|
19
|
+
this.attributes = new Map();
|
|
20
|
+
if (reverse !== null) this.attributes.set('render-reverse', reverse);
|
|
21
|
+
}
|
|
22
|
+
getAttribute(name) { return this.attributes.get(name) ?? null; }
|
|
23
|
+
setAttribute(name, value) { this.attributes.set(name, value); }
|
|
24
|
+
insertAdjacentElement(position, element) {
|
|
25
|
+
assert.ok(['beforebegin', 'afterend'].includes(position));
|
|
26
|
+
const previous = siblings.indexOf(element);
|
|
27
|
+
if (previous >= 0) siblings.splice(previous, 1);
|
|
28
|
+
siblings.splice(siblings.indexOf(this) + (position === 'afterend' ? 1 : 0), 0, element);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const template = new Element(templateReverse);
|
|
33
|
+
template.setAttribute('render-as', 'region');
|
|
34
|
+
siblings.push(template);
|
|
35
|
+
const templateData = { element: template, clones: new Map(), parent: {} };
|
|
36
|
+
if (withSource) templateData.source = { element: new Element(sourceReverse) };
|
|
37
|
+
|
|
38
|
+
const context = vm.createContext({
|
|
39
|
+
template,
|
|
40
|
+
data: { regions: regions.map(region => ({ region })) },
|
|
41
|
+
renderedNodes: new Map([[template, templateData]]),
|
|
42
|
+
HTMLTemplateElement: class {},
|
|
43
|
+
uuid: () => String(++nextId),
|
|
44
|
+
getRenderValue: (element, data, key) => data[key],
|
|
45
|
+
getRenderContext: () => undefined,
|
|
46
|
+
setRenderContext: (node, context) => context,
|
|
47
|
+
cloneTemplate: () => ({ element: new Element() }),
|
|
48
|
+
// Value interpolation is independent of list insertion and reverse settings.
|
|
49
|
+
renderValues: async (element, data) => { element.region = data.region.region; }
|
|
50
|
+
});
|
|
51
|
+
vm.runInContext(`${renderTemplate}\n${insertElement}\nglobalThis.done = renderTemplate(template, data, 'regions');`, context);
|
|
52
|
+
await context.done;
|
|
53
|
+
return siblings.filter(element => element !== template).map(element => element.region);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
test('nested region arrays preserve input order without a source or reverse attribute', async () => {
|
|
57
|
+
assert.deepEqual(await renderOrder(), regions);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('a source with no reverse attribute preserves input order', async () => {
|
|
61
|
+
assert.deepEqual(await renderOrder({ withSource: true }), regions);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('false reverse settings preserve input order', async t => {
|
|
65
|
+
for (const value of [false, 'false']) {
|
|
66
|
+
await t.test(String(value) + ' (' + typeof value + ')', async () => {
|
|
67
|
+
assert.deepEqual(await renderOrder({ templateReverse: value }), regions);
|
|
68
|
+
assert.deepEqual(await renderOrder({ withSource: true, sourceReverse: value }), regions);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('explicit reverse settings still reverse the list', async t => {
|
|
74
|
+
for (const value of ['', true, 'true']) {
|
|
75
|
+
await t.test(JSON.stringify(value), async () => {
|
|
76
|
+
const expected = [...regions].reverse();
|
|
77
|
+
assert.deepEqual(await renderOrder({ templateReverse: value }), expected);
|
|
78
|
+
assert.deepEqual(await renderOrder({ withSource: true, sourceReverse: value }), expected);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('template reverse settings apply when the source attribute is absent', async () => {
|
|
84
|
+
assert.deepEqual(await renderOrder({ withSource: true, templateReverse: '' }), [...regions].reverse());
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('an explicit source setting takes precedence over the template', async () => {
|
|
88
|
+
assert.deepEqual(await renderOrder({ withSource: true, sourceReverse: 'false', templateReverse: '' }), regions);
|
|
89
|
+
assert.deepEqual(await renderOrder({ withSource: true, sourceReverse: '', templateReverse: 'false' }), [...regions].reverse());
|
|
90
|
+
});
|