@microsoft/fast-html 1.0.0-alpha.17 → 1.0.0-alpha.19
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/README.md +11 -17
- package/dist/dts/components/index.d.ts +1 -0
- package/dist/dts/components/observer-map.d.ts +143 -0
- package/dist/dts/components/observer-map.spec.d.ts +1 -0
- package/dist/dts/components/schema.d.ts +126 -0
- package/dist/dts/components/schema.spec.d.ts +1 -0
- package/dist/dts/components/template.d.ts +11 -0
- package/dist/dts/components/utilities.d.ts +60 -1
- package/dist/dts/fixtures/observer-map/main.d.ts +1 -0
- package/dist/dts/fixtures/observer-map/observer-map.spec.d.ts +1 -0
- package/dist/dts/index.d.ts +1 -1
- package/dist/esm/components/index.js +1 -0
- package/dist/esm/components/observer-map.js +551 -0
- package/dist/esm/components/observer-map.spec.js +613 -0
- package/dist/esm/components/schema.js +196 -0
- package/dist/esm/components/schema.spec.js +248 -0
- package/dist/esm/components/template.js +104 -86
- package/dist/esm/components/utilities.js +301 -50
- package/dist/esm/components/utilities.spec.js +109 -1
- package/dist/esm/fixtures/dot-syntax/dot-syntax.spec.js +109 -2
- package/dist/esm/fixtures/dot-syntax/main.js +27 -2
- package/dist/esm/fixtures/event/main.js +3 -3
- package/dist/esm/fixtures/observer-map/main.js +304 -0
- package/dist/esm/fixtures/observer-map/observer-map.spec.js +174 -0
- package/dist/esm/index.js +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/fast-html.api.json +212 -0
- package/dist/fast-html.d.ts +207 -0
- package/dist/fast-html.untrimmed.d.ts +207 -0
- package/package.json +5 -4
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Observable } from "@microsoft/fast-element/observable.js";
|
|
1
2
|
const openClientSideBinding = "{";
|
|
2
3
|
const closeClientSideBinding = "}";
|
|
3
4
|
const openContentBinding = "{{";
|
|
@@ -251,8 +252,28 @@ export function getAllPartials(innerHTML, offset = 0, partials = {}) {
|
|
|
251
252
|
* @returns A function to access the value from a given path.
|
|
252
253
|
*/
|
|
253
254
|
export function pathResolver(path, self = false) {
|
|
254
|
-
let splitPath =
|
|
255
|
-
|
|
255
|
+
let splitPath = [];
|
|
256
|
+
path.split("../").forEach((pathItem, index) => {
|
|
257
|
+
if (pathItem === "") {
|
|
258
|
+
splitPath.unshift("../");
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
splitPath.push(...pathItem.split("."));
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
splitPath = splitPath.map((pathItem, index) => {
|
|
265
|
+
if (pathItem === "../") {
|
|
266
|
+
if (splitPath[index + 1] === "../") {
|
|
267
|
+
return "parentContext";
|
|
268
|
+
}
|
|
269
|
+
return "parent";
|
|
270
|
+
}
|
|
271
|
+
return pathItem;
|
|
272
|
+
});
|
|
273
|
+
return pathWithContextResolver(splitPath, self);
|
|
274
|
+
}
|
|
275
|
+
function pathWithContextResolver(splitPath, self) {
|
|
276
|
+
const usesContext = splitPath[0] === "parent" || splitPath[0] === "parentContext";
|
|
256
277
|
if (self && !usesContext) {
|
|
257
278
|
if (splitPath.length > 1) {
|
|
258
279
|
splitPath = splitPath.slice(1);
|
|
@@ -263,31 +284,78 @@ export function pathResolver(path, self = false) {
|
|
|
263
284
|
};
|
|
264
285
|
}
|
|
265
286
|
}
|
|
266
|
-
if (
|
|
267
|
-
return (accessibleObject) => {
|
|
268
|
-
return accessibleObject === null || accessibleObject === void 0 ? void 0 : accessibleObject[splitPath[0]];
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
return (accessibleObject, context) => {
|
|
272
|
-
if (usesContext) {
|
|
273
|
-
splitPath = [];
|
|
274
|
-
path.split("../").forEach(pathItem => {
|
|
275
|
-
if (pathItem === "") {
|
|
276
|
-
splitPath.unshift("parent");
|
|
277
|
-
}
|
|
278
|
-
else {
|
|
279
|
-
splitPath.push(...pathItem.split("."));
|
|
280
|
-
}
|
|
281
|
-
});
|
|
287
|
+
if (usesContext) {
|
|
288
|
+
return (accessibleObject, context) => {
|
|
282
289
|
return splitPath.reduce((previousAccessors, pathItem) => {
|
|
283
290
|
return previousAccessors === null || previousAccessors === void 0 ? void 0 : previousAccessors[pathItem];
|
|
284
291
|
}, context);
|
|
285
|
-
}
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
return (accessibleObject) => {
|
|
286
295
|
return splitPath.reduce((previousAccessors, pathItem) => {
|
|
287
296
|
return previousAccessors === null || previousAccessors === void 0 ? void 0 : previousAccessors[pathItem];
|
|
288
297
|
}, accessibleObject);
|
|
289
298
|
};
|
|
290
299
|
}
|
|
300
|
+
export function bindingResolver(path, self = false, parentContext, type, observerMap, contextPath, level) {
|
|
301
|
+
// Cache path during template processing when ObserverMap is provided
|
|
302
|
+
if (observerMap) {
|
|
303
|
+
observerMap.cachePathWithContext({
|
|
304
|
+
path,
|
|
305
|
+
self,
|
|
306
|
+
parentContext,
|
|
307
|
+
contextPath,
|
|
308
|
+
type,
|
|
309
|
+
level,
|
|
310
|
+
rootPath: null,
|
|
311
|
+
context: null,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
return pathResolver(path, self);
|
|
315
|
+
}
|
|
316
|
+
export function expressionResolver(self, expression, parentContext, level, observerMap) {
|
|
317
|
+
// Cache paths from expression during template processing when ObserverMap is provided
|
|
318
|
+
if (observerMap) {
|
|
319
|
+
const paths = extractPathsFromChainedExpression(expression);
|
|
320
|
+
paths.forEach(path => {
|
|
321
|
+
observerMap.cachePathWithContext({
|
|
322
|
+
path,
|
|
323
|
+
self,
|
|
324
|
+
parentContext,
|
|
325
|
+
contextPath: null,
|
|
326
|
+
type: "access",
|
|
327
|
+
level,
|
|
328
|
+
rootPath: null,
|
|
329
|
+
context: null,
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
return (x, c) => resolveChainedExpression(x, c, self, expression);
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Extracts all paths from a ChainedExpression, including nested expressions
|
|
337
|
+
* @param chainedExpression - The chained expression to extract paths from
|
|
338
|
+
* @returns A Set containing all unique paths found in the expression chain
|
|
339
|
+
*/
|
|
340
|
+
export function extractPathsFromChainedExpression(chainedExpression) {
|
|
341
|
+
const paths = new Set();
|
|
342
|
+
function processExpression(expr) {
|
|
343
|
+
// Check left operand (only add if it's not a literal value)
|
|
344
|
+
if (typeof expr.left === "string" && !expr.leftIsValue) {
|
|
345
|
+
paths.add(expr.left);
|
|
346
|
+
}
|
|
347
|
+
// Check right operand (only add if it's not a literal value)
|
|
348
|
+
if (typeof expr.right === "string" && !expr.rightIsValue) {
|
|
349
|
+
paths.add(expr.right);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
let current = chainedExpression;
|
|
353
|
+
while (current !== undefined) {
|
|
354
|
+
processExpression(current.expression);
|
|
355
|
+
current = current.next;
|
|
356
|
+
}
|
|
357
|
+
return paths;
|
|
358
|
+
}
|
|
291
359
|
/**
|
|
292
360
|
* Determine if the operand is a value (boolean, number, string) or an accessor.
|
|
293
361
|
* @param operand
|
|
@@ -308,42 +376,79 @@ function isOperandValue(operand) {
|
|
|
308
376
|
};
|
|
309
377
|
}
|
|
310
378
|
}
|
|
379
|
+
/**
|
|
380
|
+
* Evaluates parts of an expression chain
|
|
381
|
+
* @param parts Each part of an expression chain
|
|
382
|
+
* @param operator The operator between expressions
|
|
383
|
+
* @returns
|
|
384
|
+
*/
|
|
385
|
+
function evaluatePartsInExpressionChain(parts, operator) {
|
|
386
|
+
// Process each part recursively and chain them with ||
|
|
387
|
+
const firstPart = getExpressionChain(parts[0]);
|
|
388
|
+
if (firstPart) {
|
|
389
|
+
let current = firstPart;
|
|
390
|
+
for (let i = 1; i < parts.length; i++) {
|
|
391
|
+
const nextPart = getExpressionChain(parts[i]);
|
|
392
|
+
if (nextPart) {
|
|
393
|
+
// Find the end of the current chain
|
|
394
|
+
while (current.next) {
|
|
395
|
+
current = current.next;
|
|
396
|
+
}
|
|
397
|
+
current.next = Object.assign({ operator }, nextPart);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return firstPart;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
311
403
|
/**
|
|
312
404
|
* Gets the expression chain as a configuration object
|
|
313
405
|
* @param value - The binding string value
|
|
314
406
|
* @returns - A configuration object containing information about the expression
|
|
315
407
|
*/
|
|
316
408
|
export function getExpressionChain(value) {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
if (
|
|
323
|
-
|
|
324
|
-
expression: getExpression(expressionString),
|
|
325
|
-
next: Object.assign({ operator: splitItem }, getExpressionChain(split.slice(index + 1).join(" "))),
|
|
326
|
-
};
|
|
409
|
+
// Handle operator precedence: || has lower precedence than &&
|
|
410
|
+
// First, split by || (lowest precedence)
|
|
411
|
+
const orParts = value.split(" || ");
|
|
412
|
+
if (orParts.length > 1) {
|
|
413
|
+
const firstPart = evaluatePartsInExpressionChain(orParts, "||");
|
|
414
|
+
if (firstPart) {
|
|
415
|
+
return firstPart;
|
|
327
416
|
}
|
|
328
|
-
|
|
329
|
-
|
|
417
|
+
}
|
|
418
|
+
// If no ||, check for && (higher precedence)
|
|
419
|
+
const andParts = value.split(" && ");
|
|
420
|
+
if (andParts.length > 1) {
|
|
421
|
+
// Process each part recursively and chain them with &&
|
|
422
|
+
const firstPart = evaluatePartsInExpressionChain(andParts, "&&");
|
|
423
|
+
if (firstPart) {
|
|
424
|
+
return firstPart;
|
|
330
425
|
}
|
|
331
|
-
});
|
|
332
|
-
if (chainedExpression) {
|
|
333
|
-
return chainedExpression;
|
|
334
426
|
}
|
|
335
|
-
|
|
427
|
+
// Handle HTML entity version of &&
|
|
428
|
+
const ampParts = value.split(" && ");
|
|
429
|
+
if (ampParts.length > 1) {
|
|
430
|
+
// Process each part recursively and chain them with &&
|
|
431
|
+
const firstPart = evaluatePartsInExpressionChain(ampParts, "&&");
|
|
432
|
+
if (firstPart) {
|
|
433
|
+
return firstPart;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
// No chaining operators found, create a single expression
|
|
437
|
+
if (value.trim()) {
|
|
336
438
|
return {
|
|
337
|
-
expression: getExpression(
|
|
439
|
+
expression: getExpression(value.trim()),
|
|
338
440
|
};
|
|
339
441
|
}
|
|
340
442
|
return void 0;
|
|
341
443
|
}
|
|
342
444
|
function getExpression(value) {
|
|
343
445
|
if (value[0] === "!") {
|
|
446
|
+
const left = value.slice(1);
|
|
447
|
+
const operandValue = isOperandValue(left);
|
|
344
448
|
return {
|
|
345
449
|
operator: "!",
|
|
346
|
-
left
|
|
450
|
+
left,
|
|
451
|
+
leftIsValue: operandValue.isValue,
|
|
347
452
|
right: null,
|
|
348
453
|
rightIsValue: null,
|
|
349
454
|
};
|
|
@@ -351,17 +456,22 @@ function getExpression(value) {
|
|
|
351
456
|
const split = value.split(" ");
|
|
352
457
|
if (split.length === 3) {
|
|
353
458
|
const operator = split[1];
|
|
354
|
-
const
|
|
459
|
+
const right = split[2];
|
|
460
|
+
const rightOperandValue = isOperandValue(right);
|
|
461
|
+
const left = split[0];
|
|
462
|
+
const leftOperandValue = isOperandValue(left);
|
|
355
463
|
return {
|
|
356
464
|
operator,
|
|
357
465
|
left: split[0],
|
|
358
|
-
|
|
359
|
-
|
|
466
|
+
leftIsValue: leftOperandValue.isValue,
|
|
467
|
+
right: rightOperandValue.isValue ? rightOperandValue.value : right,
|
|
468
|
+
rightIsValue: rightOperandValue.isValue,
|
|
360
469
|
};
|
|
361
470
|
}
|
|
362
471
|
return {
|
|
363
472
|
operator: "access",
|
|
364
473
|
left: value,
|
|
474
|
+
leftIsValue: false,
|
|
365
475
|
right: null,
|
|
366
476
|
rightIsValue: null,
|
|
367
477
|
};
|
|
@@ -410,19 +520,19 @@ function resolveExpression(x, c, self, expression) {
|
|
|
410
520
|
* @param next - The next expression to be chained
|
|
411
521
|
* @returns - A resolved return value for a binding
|
|
412
522
|
*/
|
|
413
|
-
function resolveChainedExpression(x, c, self, expression
|
|
414
|
-
if (next) {
|
|
415
|
-
switch (next.operator) {
|
|
523
|
+
function resolveChainedExpression(x, c, self, expression) {
|
|
524
|
+
if (expression.next) {
|
|
525
|
+
switch (expression.next.operator) {
|
|
416
526
|
case "&&":
|
|
417
527
|
case "&&":
|
|
418
|
-
return (resolveExpression(x, c, self, expression) &&
|
|
419
|
-
resolveChainedExpression(x, c, self,
|
|
528
|
+
return (resolveExpression(x, c, self, expression.expression) &&
|
|
529
|
+
resolveChainedExpression(x, c, self, expression.next));
|
|
420
530
|
case "||":
|
|
421
|
-
return (resolveExpression(x, c, self, expression) ||
|
|
422
|
-
resolveChainedExpression(x, c, self,
|
|
531
|
+
return (resolveExpression(x, c, self, expression.expression) ||
|
|
532
|
+
resolveChainedExpression(x, c, self, expression.next));
|
|
423
533
|
}
|
|
424
534
|
}
|
|
425
|
-
return resolveExpression(x, c, self, expression);
|
|
535
|
+
return resolveExpression(x, c, self, expression.expression);
|
|
426
536
|
}
|
|
427
537
|
/**
|
|
428
538
|
* This is the transform utility for rationalizing declarative HTML syntax
|
|
@@ -458,8 +568,149 @@ export function transformInnerHTML(innerHTML, index = 0) {
|
|
|
458
568
|
* @param self - Where the first item in the path path refers to the item itself (used by repeat).
|
|
459
569
|
* @param chainedExpression - The chained expression which includes the expression and the next expression
|
|
460
570
|
* if there is another in the chain
|
|
571
|
+
* @param observerMap - Optional ObserverMap instance for caching paths during template processing
|
|
461
572
|
* @returns - A binding that resolves the chained expression logic
|
|
462
573
|
*/
|
|
463
|
-
export function resolveWhen(self,
|
|
464
|
-
|
|
574
|
+
export function resolveWhen(self, expression, parentContext, level, observerMap) {
|
|
575
|
+
const binding = expressionResolver(self, expression, parentContext, level, observerMap);
|
|
576
|
+
return (x, c) => binding(x, c);
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Helper function to determine the data type of an object property
|
|
580
|
+
*/
|
|
581
|
+
function getDataType(object) {
|
|
582
|
+
if (Array.isArray(object))
|
|
583
|
+
return "array";
|
|
584
|
+
if (typeof object === "object" && object !== null)
|
|
585
|
+
return "object";
|
|
586
|
+
return "primitive";
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Get the next property
|
|
590
|
+
* @param path The dot syntax data path
|
|
591
|
+
* @returns The next property
|
|
592
|
+
*/
|
|
593
|
+
export function getNextProperty(path) {
|
|
594
|
+
return path.split(".")[0];
|
|
595
|
+
}
|
|
596
|
+
function sortByDeepestNestingItem(first, second) {
|
|
597
|
+
const firstRelativePathLength = first.length;
|
|
598
|
+
const secondRelativePathLength = second.length;
|
|
599
|
+
return firstRelativePathLength > secondRelativePathLength
|
|
600
|
+
? -1
|
|
601
|
+
: secondRelativePathLength > firstRelativePathLength
|
|
602
|
+
? 1
|
|
603
|
+
: 0;
|
|
604
|
+
}
|
|
605
|
+
function assignObservablesToArray(proxiedData, cachePath) {
|
|
606
|
+
const data = proxiedData.map((item) => {
|
|
607
|
+
const originalItem = Object.assign({}, item);
|
|
608
|
+
assignProxyToItemsInArray(item, originalItem, cachePath);
|
|
609
|
+
return Object.assign(item, originalItem);
|
|
610
|
+
});
|
|
611
|
+
Observable.getNotifier(data).subscribe({
|
|
612
|
+
handleChange(subject, args) {
|
|
613
|
+
args.forEach((arg) => {
|
|
614
|
+
if (arg.addedCount > 0) {
|
|
615
|
+
for (let i = arg.addedCount - 1; i >= 0; i--) {
|
|
616
|
+
const item = subject[arg.index + i];
|
|
617
|
+
const originalItem = Object.assign({}, item);
|
|
618
|
+
assignProxyToItemsInArray(item, originalItem, cachePath);
|
|
619
|
+
return Object.assign(item, originalItem);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
});
|
|
623
|
+
},
|
|
624
|
+
});
|
|
625
|
+
return data;
|
|
626
|
+
}
|
|
627
|
+
export function assignObservables(cachePath, data, target, rootProperty) {
|
|
628
|
+
const dataType = getDataType(data);
|
|
629
|
+
let proxiedData = data;
|
|
630
|
+
switch (dataType) {
|
|
631
|
+
case "array": {
|
|
632
|
+
if (cachePath.type === "repeat") {
|
|
633
|
+
proxiedData = assignObservablesToArray(proxiedData, cachePath);
|
|
634
|
+
}
|
|
635
|
+
break;
|
|
636
|
+
}
|
|
637
|
+
case "object": {
|
|
638
|
+
if (cachePath.type === "default") {
|
|
639
|
+
const relativePaths = Object.values(cachePath.paths)
|
|
640
|
+
.map((value) => {
|
|
641
|
+
return value.relativePath.split(".").slice(1); // the first item is the root path
|
|
642
|
+
})
|
|
643
|
+
.sort(sortByDeepestNestingItem);
|
|
644
|
+
for (const relativePath of relativePaths) {
|
|
645
|
+
proxiedData = assignProxyToItemsInObject(relativePath, target, rootProperty, proxiedData, cachePath);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
break;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
return proxiedData;
|
|
652
|
+
}
|
|
653
|
+
function assignProxyToItemsInArray(item, originalItem, cachePath) {
|
|
654
|
+
const itemProperties = Object.keys(item);
|
|
655
|
+
itemProperties.forEach(key => {
|
|
656
|
+
Observable.defineProperty(item, key);
|
|
657
|
+
const relativePaths = Object.values(cachePath.paths).filter(pathItem => {
|
|
658
|
+
if (pathItem.type === "access") {
|
|
659
|
+
return pathItem.relativePath.startsWith(`${cachePath.context}.${key}.`);
|
|
660
|
+
}
|
|
661
|
+
return false;
|
|
662
|
+
})
|
|
663
|
+
.map(value => {
|
|
664
|
+
return value.relativePath.split(".").slice(2); // the first item is the context, the next is the property
|
|
665
|
+
})
|
|
666
|
+
.sort(sortByDeepestNestingItem);
|
|
667
|
+
for (const relativePath of relativePaths) {
|
|
668
|
+
originalItem[key] = assignProxyToItemsInObject(relativePath, item, key, originalItem[key], cachePath);
|
|
669
|
+
}
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
function assignProxyToItemsInObject(paths, target, rootProperty, data, cachePath) {
|
|
673
|
+
const type = getDataType(data);
|
|
674
|
+
let proxiedData = data;
|
|
675
|
+
if (type === "object") {
|
|
676
|
+
// navigate through all items in the object
|
|
677
|
+
proxiedData[paths[0]] = assignProxyToItemsInObject(paths.slice(1), target, rootProperty, proxiedData[paths[0]], cachePath);
|
|
678
|
+
// assign a Proxy to the object
|
|
679
|
+
proxiedData = assignProxy(cachePath, target, rootProperty, data);
|
|
680
|
+
}
|
|
681
|
+
else if (type === "array") {
|
|
682
|
+
if (cachePath.type === "repeat") {
|
|
683
|
+
proxiedData = assignObservablesToArray(proxiedData, cachePath.paths[rootProperty]);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
return proxiedData;
|
|
687
|
+
}
|
|
688
|
+
export function assignProxy(cachePath, target, rootProperty, object) {
|
|
689
|
+
if (object.$isProxy === undefined) {
|
|
690
|
+
// Create a proxy for the object that triggers Observable.notify on mutations
|
|
691
|
+
return new Proxy(object, {
|
|
692
|
+
set: (obj, prop, value) => {
|
|
693
|
+
obj[prop] = assignObservables(cachePath, value, target, rootProperty);
|
|
694
|
+
// Trigger notification for property changes
|
|
695
|
+
Observable.notify(target, rootProperty);
|
|
696
|
+
return true;
|
|
697
|
+
},
|
|
698
|
+
get: (target, key) => {
|
|
699
|
+
if (key !== "$isProxy") {
|
|
700
|
+
return target[key];
|
|
701
|
+
}
|
|
702
|
+
return true;
|
|
703
|
+
},
|
|
704
|
+
deleteProperty: (obj, prop) => {
|
|
705
|
+
if (prop in obj) {
|
|
706
|
+
delete obj[prop];
|
|
707
|
+
// Trigger notification for property deletion
|
|
708
|
+
Observable.notify(target, rootProperty);
|
|
709
|
+
return true;
|
|
710
|
+
}
|
|
711
|
+
return false;
|
|
712
|
+
},
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
return object;
|
|
465
716
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { __awaiter } from "tslib";
|
|
2
2
|
import { expect, test } from "@playwright/test";
|
|
3
|
-
import { getNextBehavior, getAllPartials, getIndexOfNextMatchingTag, pathResolver, transformInnerHTML, getExpressionChain } from "./utilities.js";
|
|
3
|
+
import { getNextBehavior, getAllPartials, getIndexOfNextMatchingTag, pathResolver, transformInnerHTML, getExpressionChain, extractPathsFromChainedExpression, } from "./utilities.js";
|
|
4
4
|
test.describe("utilities", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
5
5
|
test.describe("content", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
6
6
|
test("get the next content binding", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -192,6 +192,7 @@ test.describe("utilities", () => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
192
192
|
expression: {
|
|
193
193
|
operator: "access",
|
|
194
194
|
left: "foo",
|
|
195
|
+
leftIsValue: false,
|
|
195
196
|
right: null,
|
|
196
197
|
rightIsValue: null,
|
|
197
198
|
}
|
|
@@ -202,6 +203,7 @@ test.describe("utilities", () => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
202
203
|
expression: {
|
|
203
204
|
operator: "!",
|
|
204
205
|
left: "foo",
|
|
206
|
+
leftIsValue: false,
|
|
205
207
|
right: null,
|
|
206
208
|
rightIsValue: null,
|
|
207
209
|
}
|
|
@@ -212,6 +214,7 @@ test.describe("utilities", () => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
212
214
|
expression: {
|
|
213
215
|
operator: "!=",
|
|
214
216
|
left: "foo",
|
|
217
|
+
leftIsValue: false,
|
|
215
218
|
right: "test",
|
|
216
219
|
rightIsValue: true,
|
|
217
220
|
}
|
|
@@ -222,6 +225,7 @@ test.describe("utilities", () => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
222
225
|
expression: {
|
|
223
226
|
operator: "!=",
|
|
224
227
|
left: "foo",
|
|
228
|
+
leftIsValue: false,
|
|
225
229
|
right: false,
|
|
226
230
|
rightIsValue: true,
|
|
227
231
|
}
|
|
@@ -232,6 +236,7 @@ test.describe("utilities", () => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
232
236
|
expression: {
|
|
233
237
|
operator: "!=",
|
|
234
238
|
left: "foo",
|
|
239
|
+
leftIsValue: false,
|
|
235
240
|
right: 5,
|
|
236
241
|
rightIsValue: true,
|
|
237
242
|
}
|
|
@@ -242,6 +247,7 @@ test.describe("utilities", () => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
242
247
|
expression: {
|
|
243
248
|
operator: "!=",
|
|
244
249
|
left: "foo",
|
|
250
|
+
leftIsValue: false,
|
|
245
251
|
right: "bat",
|
|
246
252
|
rightIsValue: true,
|
|
247
253
|
},
|
|
@@ -250,6 +256,7 @@ test.describe("utilities", () => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
250
256
|
expression: {
|
|
251
257
|
operator: "==",
|
|
252
258
|
left: "bar",
|
|
259
|
+
leftIsValue: false,
|
|
253
260
|
right: "baz",
|
|
254
261
|
rightIsValue: true,
|
|
255
262
|
}
|
|
@@ -259,6 +266,7 @@ test.describe("utilities", () => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
259
266
|
expression: {
|
|
260
267
|
operator: "access",
|
|
261
268
|
left: "foo",
|
|
269
|
+
leftIsValue: false,
|
|
262
270
|
right: null,
|
|
263
271
|
rightIsValue: null,
|
|
264
272
|
},
|
|
@@ -267,6 +275,7 @@ test.describe("utilities", () => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
267
275
|
expression: {
|
|
268
276
|
operator: "access",
|
|
269
277
|
left: "bar",
|
|
278
|
+
leftIsValue: false,
|
|
270
279
|
right: null,
|
|
271
280
|
rightIsValue: null,
|
|
272
281
|
}
|
|
@@ -274,4 +283,103 @@ test.describe("utilities", () => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
274
283
|
});
|
|
275
284
|
}));
|
|
276
285
|
}));
|
|
286
|
+
test.describe("extractPathsFromChainedExpression", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
287
|
+
test("should extract paths from simple access expression", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
288
|
+
const expressionChain = getExpressionChain("user");
|
|
289
|
+
expect(expressionChain).toBeDefined();
|
|
290
|
+
const paths = extractPathsFromChainedExpression(expressionChain);
|
|
291
|
+
expect(paths.size).toEqual(1);
|
|
292
|
+
expect(paths.has("user")).toBe(true);
|
|
293
|
+
}));
|
|
294
|
+
test("should extract paths from dot notation expression", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
295
|
+
const expressionChain = getExpressionChain("user.name");
|
|
296
|
+
expect(expressionChain).toBeDefined();
|
|
297
|
+
const paths = extractPathsFromChainedExpression(expressionChain);
|
|
298
|
+
expect(paths.size).toEqual(1);
|
|
299
|
+
expect(paths.has("user.name")).toBe(true);
|
|
300
|
+
}));
|
|
301
|
+
test("should extract paths from comparison with literal values", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
302
|
+
const expressionChain = getExpressionChain("user.age > 18");
|
|
303
|
+
expect(expressionChain).toBeDefined();
|
|
304
|
+
const paths = extractPathsFromChainedExpression(expressionChain);
|
|
305
|
+
expect(paths.size).toEqual(1);
|
|
306
|
+
expect(paths.has("user.age")).toBe(true);
|
|
307
|
+
}));
|
|
308
|
+
test("should extract paths from comparison between two properties", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
309
|
+
const expressionChain = getExpressionChain("user.age >= admin.minAge");
|
|
310
|
+
expect(expressionChain).toBeDefined();
|
|
311
|
+
const paths = extractPathsFromChainedExpression(expressionChain);
|
|
312
|
+
expect(paths.size).toEqual(2);
|
|
313
|
+
expect(paths.has("user.age")).toBe(true);
|
|
314
|
+
expect(paths.has("admin.minAge")).toBe(true);
|
|
315
|
+
}));
|
|
316
|
+
test("should extract paths from chained AND expressions", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
317
|
+
const expressionChain = getExpressionChain("isActive && user.name");
|
|
318
|
+
expect(expressionChain).toBeDefined();
|
|
319
|
+
const paths = extractPathsFromChainedExpression(expressionChain);
|
|
320
|
+
expect(paths.size).toEqual(2);
|
|
321
|
+
expect(paths.has("isActive")).toBe(true);
|
|
322
|
+
expect(paths.has("user.name")).toBe(true);
|
|
323
|
+
}));
|
|
324
|
+
test("should extract paths from chained OR expressions", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
325
|
+
const expressionChain = getExpressionChain("user.isAdmin || permissions.canEdit");
|
|
326
|
+
expect(expressionChain).toBeDefined();
|
|
327
|
+
const paths = extractPathsFromChainedExpression(expressionChain);
|
|
328
|
+
expect(paths.size).toEqual(2);
|
|
329
|
+
expect(paths.has("user.isAdmin")).toBe(true);
|
|
330
|
+
expect(paths.has("permissions.canEdit")).toBe(true);
|
|
331
|
+
}));
|
|
332
|
+
test("should extract paths from complex chained expressions", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
333
|
+
const expressionChain = getExpressionChain("user.age > 18 && user.status == 'active' || admin.override");
|
|
334
|
+
expect(expressionChain).toBeDefined();
|
|
335
|
+
const paths = extractPathsFromChainedExpression(expressionChain);
|
|
336
|
+
expect(paths.size).toEqual(3);
|
|
337
|
+
expect(paths.has("user.age")).toBe(true);
|
|
338
|
+
expect(paths.has("user.status")).toBe(true);
|
|
339
|
+
expect(paths.has("admin.override")).toBe(true);
|
|
340
|
+
}));
|
|
341
|
+
test("should extract paths from NOT expressions", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
342
|
+
const expressionChain = getExpressionChain("!user.isDisabled");
|
|
343
|
+
expect(expressionChain).toBeDefined();
|
|
344
|
+
const paths = extractPathsFromChainedExpression(expressionChain);
|
|
345
|
+
expect(paths.size).toEqual(1);
|
|
346
|
+
expect(paths.has("user.isDisabled")).toBe(true);
|
|
347
|
+
}));
|
|
348
|
+
test("should handle expressions with only literal values", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
349
|
+
const expressionChain = getExpressionChain("5 > 3");
|
|
350
|
+
expect(expressionChain).toBeDefined();
|
|
351
|
+
const paths = extractPathsFromChainedExpression(expressionChain);
|
|
352
|
+
expect(paths.size).toEqual(0);
|
|
353
|
+
}));
|
|
354
|
+
test("should handle mixed literal and property expressions", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
355
|
+
const expressionChain = getExpressionChain("count > 0 && status == 'ready'");
|
|
356
|
+
expect(expressionChain).toBeDefined();
|
|
357
|
+
const paths = extractPathsFromChainedExpression(expressionChain);
|
|
358
|
+
expect(paths.size).toEqual(2);
|
|
359
|
+
expect(paths.has("count")).toBe(true);
|
|
360
|
+
expect(paths.has("status")).toBe(true);
|
|
361
|
+
}));
|
|
362
|
+
test("should deduplicate identical paths", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
363
|
+
const expressionChain = getExpressionChain("user.name && user.name != 'anonymous'");
|
|
364
|
+
expect(expressionChain).toBeDefined();
|
|
365
|
+
const paths = extractPathsFromChainedExpression(expressionChain);
|
|
366
|
+
expect(paths.size).toEqual(1);
|
|
367
|
+
expect(paths.has("user.name")).toBe(true);
|
|
368
|
+
}));
|
|
369
|
+
test("should handle HTML entity operators", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
370
|
+
const expressionChain = getExpressionChain("isValid && data.ready");
|
|
371
|
+
expect(expressionChain).toBeDefined();
|
|
372
|
+
const paths = extractPathsFromChainedExpression(expressionChain);
|
|
373
|
+
expect(paths.size).toEqual(2);
|
|
374
|
+
expect(paths.has("isValid")).toBe(true);
|
|
375
|
+
expect(paths.has("data.ready")).toBe(true);
|
|
376
|
+
}));
|
|
377
|
+
test("should handle deeply nested property paths", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
378
|
+
const expressionChain = getExpressionChain("app.user.profile.settings.theme");
|
|
379
|
+
expect(expressionChain).toBeDefined();
|
|
380
|
+
const paths = extractPathsFromChainedExpression(expressionChain);
|
|
381
|
+
expect(paths.size).toEqual(1);
|
|
382
|
+
expect(paths.has("app.user.profile.settings.theme")).toBe(true);
|
|
383
|
+
}));
|
|
384
|
+
}));
|
|
277
385
|
}));
|