@reearth/core 0.0.7-alpha.60 → 0.0.7-alpha.62
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/core.js +8154 -8145
- package/dist/core.umd.cjs +69 -69
- package/package.json +1 -1
- package/src/Map/Layers/hooks.ts +10 -4
- package/src/mantle/evaluator/simple/expression/README.md +392 -0
- package/src/mantle/evaluator/simple/expression/expression.test.ts +65 -0
- package/src/mantle/evaluator/simple/expression/variableReplacer.test.ts +178 -0
- package/src/mantle/evaluator/simple/expression/variableReplacer.ts +24 -11
package/package.json
CHANGED
package/src/Map/Layers/hooks.ts
CHANGED
|
@@ -204,6 +204,12 @@ export default function useHooks({
|
|
|
204
204
|
),
|
|
205
205
|
);
|
|
206
206
|
|
|
207
|
+
// Store getComputedLayer in a ref to avoid recreating prototypes on every change
|
|
208
|
+
const getComputedLayerRef = useRef(getComputedLayer);
|
|
209
|
+
useEffect(() => {
|
|
210
|
+
getComputedLayerRef.current = getComputedLayer;
|
|
211
|
+
}, [getComputedLayer]);
|
|
212
|
+
|
|
207
213
|
const lazyComputedLayerPrototype = useMemo<object>(() => {
|
|
208
214
|
return objectFromGetter(
|
|
209
215
|
// id and layer should not be accessible
|
|
@@ -212,12 +218,12 @@ export default function useHooks({
|
|
|
212
218
|
const id: string | undefined = (this as any).id;
|
|
213
219
|
if (!id || typeof id !== "string") throw new Error("layer ID is not specified");
|
|
214
220
|
|
|
215
|
-
const layer =
|
|
221
|
+
const layer = getComputedLayerRef.current(id);
|
|
216
222
|
if (!layer) return undefined;
|
|
217
223
|
return (layer as any)[key];
|
|
218
224
|
},
|
|
219
225
|
);
|
|
220
|
-
}, [
|
|
226
|
+
}, []);
|
|
221
227
|
|
|
222
228
|
const lazyLayerPrototype = useMemo<object>(() => {
|
|
223
229
|
return objectFromGetter(layerKeys, function (key) {
|
|
@@ -237,7 +243,7 @@ export default function useHooks({
|
|
|
237
243
|
else if (key === "isVisible") return layer.visible;
|
|
238
244
|
// computed
|
|
239
245
|
else if (key === "computed") {
|
|
240
|
-
const computedLayer =
|
|
246
|
+
const computedLayer = getComputedLayerRef.current(layer.id);
|
|
241
247
|
if (!computedLayer) return undefined;
|
|
242
248
|
const computed = Object.create(lazyComputedLayerPrototype);
|
|
243
249
|
computed.id = id;
|
|
@@ -246,7 +252,7 @@ export default function useHooks({
|
|
|
246
252
|
|
|
247
253
|
return (layer as any)[key];
|
|
248
254
|
});
|
|
249
|
-
}, [
|
|
255
|
+
}, [layerMap, lazyComputedLayerPrototype]);
|
|
250
256
|
|
|
251
257
|
const findById = useCallback(
|
|
252
258
|
(layerId: string): LazyLayer | undefined => {
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
# Expression Evaluator
|
|
2
|
+
|
|
3
|
+
A powerful expression evaluator for evaluating dynamic expressions with feature properties, supporting various operators, functions, and property access methods.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Basic Usage](#basic-usage)
|
|
8
|
+
- [Property Access](#property-access)
|
|
9
|
+
- [Operators](#operators)
|
|
10
|
+
- [Functions](#functions)
|
|
11
|
+
- [Data Types](#data-types)
|
|
12
|
+
- [Advanced Features](#advanced-features)
|
|
13
|
+
|
|
14
|
+
## Basic Usage
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { Expression } from "./expression";
|
|
18
|
+
|
|
19
|
+
const feature = {
|
|
20
|
+
properties: {
|
|
21
|
+
height: 100,
|
|
22
|
+
name: "Building A",
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const expr = new Expression("${height} > 50", feature);
|
|
27
|
+
const result = expr.evaluate(); // true
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Property Access
|
|
31
|
+
|
|
32
|
+
### 1. Standard Property Names (No Spaces)
|
|
33
|
+
|
|
34
|
+
For properties without spaces or special characters, use the simple syntax:
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
${propertyName}
|
|
38
|
+
${height}
|
|
39
|
+
${temperature}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Example:**
|
|
43
|
+
```typescript
|
|
44
|
+
const feature = {
|
|
45
|
+
properties: {
|
|
46
|
+
height: 100,
|
|
47
|
+
width: 50,
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const expr = new Expression("${height} * ${width}", feature);
|
|
52
|
+
expr.evaluate(); // 5000
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 2. Quoted Property Names (With Spaces)
|
|
56
|
+
|
|
57
|
+
**✨ NEW:** For properties with spaces or special characters, wrap the property name in quotes:
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
${"property name"} // Double quotes (recommended)
|
|
61
|
+
${'property name'} // Single quotes
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Example:**
|
|
65
|
+
```typescript
|
|
66
|
+
const feature = {
|
|
67
|
+
properties: {
|
|
68
|
+
"user name": "Alice",
|
|
69
|
+
"user score": 95,
|
|
70
|
+
"email@address": "alice@example.com",
|
|
71
|
+
"user-info:age": 25,
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// Access properties with spaces
|
|
76
|
+
const expr1 = new Expression('${"user name"}', feature);
|
|
77
|
+
expr1.evaluate(); // "Alice"
|
|
78
|
+
|
|
79
|
+
// Use in conditionals
|
|
80
|
+
const expr2 = new Expression('${"user score"} > 90 ? "Excellent" : "Good"', feature);
|
|
81
|
+
expr2.evaluate(); // "Excellent"
|
|
82
|
+
|
|
83
|
+
// Arithmetic operations
|
|
84
|
+
const expr3 = new Expression('${"user-info:age"} + 5', feature);
|
|
85
|
+
expr3.evaluate(); // 30
|
|
86
|
+
|
|
87
|
+
// Properties with special characters
|
|
88
|
+
const expr4 = new Expression('${"email@address"} !== ""', feature);
|
|
89
|
+
expr4.evaluate(); // true
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Supported Characters in Quoted Names:**
|
|
93
|
+
- Spaces: `${"user name"}`
|
|
94
|
+
- Hyphens: `${"user-info"}`
|
|
95
|
+
- Colons: `${"category:name"}`
|
|
96
|
+
- At signs: `${"email@domain"}`
|
|
97
|
+
- Any other special characters
|
|
98
|
+
|
|
99
|
+
### 3. Special Variables
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
${id} // Access feature.id
|
|
103
|
+
${rootProperties} // Access entire feature.properties object
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Example:**
|
|
107
|
+
```typescript
|
|
108
|
+
const feature = {
|
|
109
|
+
id: "feature-123",
|
|
110
|
+
properties: {
|
|
111
|
+
height: 100,
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const expr = new Expression('${id}', feature);
|
|
116
|
+
expr.evaluate(); // "feature-123"
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Operators
|
|
120
|
+
|
|
121
|
+
### Comparison Operators
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
${height} > 50 // Greater than
|
|
125
|
+
${height} >= 50 // Greater than or equal
|
|
126
|
+
${height} < 100 // Less than
|
|
127
|
+
${height} <= 100 // Less than or equal
|
|
128
|
+
${height} === 50 // Strict equality
|
|
129
|
+
${height} !== 50 // Strict inequality
|
|
130
|
+
${height} == 50 // Loose equality (with type coercion)
|
|
131
|
+
${height} != 50 // Loose inequality
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Arithmetic Operators
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
${a} + ${b} // Addition
|
|
138
|
+
${a} - ${b} // Subtraction
|
|
139
|
+
${a} * ${b} // Multiplication
|
|
140
|
+
${a} / ${b} // Division
|
|
141
|
+
${a} % ${b} // Modulo
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Logical Operators
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
${a} && ${b} // Logical AND
|
|
148
|
+
${a} || ${b} // Logical OR
|
|
149
|
+
!${a} // Logical NOT
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Conditional (Ternary) Operator
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
${condition} ? ${trueValue} : ${falseValue}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Example:**
|
|
159
|
+
```typescript
|
|
160
|
+
const expr = new Expression('${height} > 100 ? "Tall" : "Short"', feature);
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Functions
|
|
164
|
+
|
|
165
|
+
### Type Conversion Functions
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
Boolean(${value}) // Convert to boolean
|
|
169
|
+
Number(${value}) // Convert to number
|
|
170
|
+
String(${value}) // Convert to string
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Math Functions
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
abs(${value}) // Absolute value
|
|
177
|
+
sqrt(${value}) // Square root
|
|
178
|
+
ceil(${value}) // Round up
|
|
179
|
+
floor(${value}) // Round down
|
|
180
|
+
round(${value}) // Round to nearest integer
|
|
181
|
+
sin(${value}) // Sine
|
|
182
|
+
cos(${value}) // Cosine
|
|
183
|
+
tan(${value}) // Tangent
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Utility Functions
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
isNaN(${value}) // Check if Not a Number
|
|
190
|
+
isFinite(${value}) // Check if finite number
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Color Functions
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
color("red") // Named color
|
|
197
|
+
color("#ff0000") // Hex color
|
|
198
|
+
color("#ff0000", 0.5) // Hex color with alpha
|
|
199
|
+
rgb(255, 0, 0) // RGB color
|
|
200
|
+
rgba(255, 0, 0, 0.5) // RGBA color
|
|
201
|
+
hsl(0, 100, 50) // HSL color
|
|
202
|
+
hsla(0, 100, 50, 0.5) // HSLA color
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
**Example:**
|
|
206
|
+
```typescript
|
|
207
|
+
const expr = new Expression('${height} > 100 ? color("red") : color("blue")', feature);
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Data Types
|
|
211
|
+
|
|
212
|
+
### Supported Types
|
|
213
|
+
|
|
214
|
+
- **Numbers**: `42`, `3.14`, `Infinity`, `NaN`
|
|
215
|
+
- **Strings**: `"hello"`, `'world'`
|
|
216
|
+
- **Booleans**: `true`, `false`
|
|
217
|
+
- **Null**: `null`
|
|
218
|
+
- **Undefined**: `undefined`
|
|
219
|
+
- **Arrays**: `[1, 2, 3]`
|
|
220
|
+
- **Colors**: Result of color functions
|
|
221
|
+
|
|
222
|
+
### Constants
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
Math.PI // 3.141592653589793
|
|
226
|
+
Math.E // 2.718281828459045
|
|
227
|
+
Number.POSITIVE_INFINITY
|
|
228
|
+
NaN
|
|
229
|
+
Infinity
|
|
230
|
+
undefined
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Advanced Features
|
|
234
|
+
|
|
235
|
+
### String Interpolation
|
|
236
|
+
|
|
237
|
+
Within string literals, you can interpolate property values:
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
"Hello, ${name}!"
|
|
241
|
+
"Height: ${height}m"
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
**Note:** String interpolation only supports simple property names (without spaces). For properties with spaces or special characters, use concatenation instead (see below).
|
|
245
|
+
|
|
246
|
+
**Example:**
|
|
247
|
+
```typescript
|
|
248
|
+
const feature = {
|
|
249
|
+
properties: {
|
|
250
|
+
name: "Building A",
|
|
251
|
+
height: 100,
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
const expr = new Expression('"Building: ${name}, Height: ${height}m"', feature);
|
|
256
|
+
expr.evaluate(); // "Building: Building A, Height: 100m"
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
**For properties with spaces, use concatenation:**
|
|
260
|
+
```typescript
|
|
261
|
+
const feature = {
|
|
262
|
+
properties: {
|
|
263
|
+
"building name": "Tower A",
|
|
264
|
+
floors: 30,
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
// Use concatenation with + operator
|
|
269
|
+
const expr = new Expression('${"building name"} + " has " + ${floors} + " floors"', feature);
|
|
270
|
+
expr.evaluate(); // "Tower A has 30 floors"
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Array Comparisons
|
|
274
|
+
|
|
275
|
+
The equality operators support checking if a value is in an array:
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
${value} == [1, 2, 3] // Check if value is in array
|
|
279
|
+
${value} != [1, 2, 3] // Check if value is not in array
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
**Example:**
|
|
283
|
+
```typescript
|
|
284
|
+
const feature = {
|
|
285
|
+
properties: {
|
|
286
|
+
status: "active",
|
|
287
|
+
},
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const expr = new Expression('${status} == ["active", "pending"]', feature);
|
|
291
|
+
expr.evaluate(); // true
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Defines (Variable Substitution)
|
|
295
|
+
|
|
296
|
+
You can define placeholder values that get substituted before evaluation:
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
const defines = {
|
|
300
|
+
MAX_HEIGHT: "100",
|
|
301
|
+
MIN_HEIGHT: "10",
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const expr = new Expression("${height} > ${MAX_HEIGHT}", feature, defines);
|
|
305
|
+
// Becomes: "${height} > 100"
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Expression Caching
|
|
309
|
+
|
|
310
|
+
Expressions are cached automatically for better performance. To clear caches:
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
import { clearExpressionCaches } from "./expression";
|
|
314
|
+
|
|
315
|
+
clearExpressionCaches(expressionString, feature, defines);
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
## Property Name Comparison
|
|
319
|
+
|
|
320
|
+
| Syntax | Use Case | Example |
|
|
321
|
+
|--------|----------|---------|
|
|
322
|
+
| `${name}` | Simple properties (no spaces) | `${height}`, `${temperature}` |
|
|
323
|
+
| `${"property name"}` | Properties with spaces/special chars | `${"user name"}`, `${"email@domain"}` |
|
|
324
|
+
| `${rootProperties}` | Access entire properties object | `${rootProperties["dynamic-key"]}` |
|
|
325
|
+
|
|
326
|
+
## Complete Example
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
import { Expression } from "./expression";
|
|
330
|
+
|
|
331
|
+
const feature = {
|
|
332
|
+
id: "building-001",
|
|
333
|
+
properties: {
|
|
334
|
+
"building name": "Tower A",
|
|
335
|
+
"building height": 150,
|
|
336
|
+
"building color": "blue",
|
|
337
|
+
floors: 30,
|
|
338
|
+
status: "active",
|
|
339
|
+
"contact info": {
|
|
340
|
+
"email address": "info@tower-a.com",
|
|
341
|
+
},
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
// Simple comparison
|
|
346
|
+
const expr1 = new Expression('${"building height"} > 100', feature);
|
|
347
|
+
console.log(expr1.evaluate()); // true
|
|
348
|
+
|
|
349
|
+
// Conditional with color
|
|
350
|
+
const expr2 = new Expression(
|
|
351
|
+
'${"building height"} > 100 ? color("red") : color("green")',
|
|
352
|
+
feature
|
|
353
|
+
);
|
|
354
|
+
console.log(expr2.evaluate()); // #ff0000 (red)
|
|
355
|
+
|
|
356
|
+
// String concatenation (for properties with spaces)
|
|
357
|
+
const expr3 = new Expression(
|
|
358
|
+
'${"building name"} + " has " + ${floors} + " floors"',
|
|
359
|
+
feature
|
|
360
|
+
);
|
|
361
|
+
console.log(expr3.evaluate()); // "Tower A has 30 floors"
|
|
362
|
+
|
|
363
|
+
// Array membership check
|
|
364
|
+
const expr4 = new Expression(
|
|
365
|
+
'${status} == ["active", "pending"]',
|
|
366
|
+
feature
|
|
367
|
+
);
|
|
368
|
+
console.log(expr4.evaluate()); // true
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
## Migration Guide
|
|
372
|
+
|
|
373
|
+
### From Standard Syntax to Quoted Syntax
|
|
374
|
+
|
|
375
|
+
If you have properties with spaces, you need to update your expressions:
|
|
376
|
+
|
|
377
|
+
**Before (doesn't work):**
|
|
378
|
+
```typescript
|
|
379
|
+
${user name} // ❌ Fails: interpreted as two separate identifiers
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
**After (works):**
|
|
383
|
+
```typescript
|
|
384
|
+
${"user name"} // ✅ Works: quoted property name
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### Best Practices
|
|
388
|
+
|
|
389
|
+
1. **Use simple syntax** for properties without spaces: `${height}`
|
|
390
|
+
2. **Use quoted syntax** for properties with spaces: `${"user name"}`
|
|
391
|
+
3. **Prefer double quotes** for consistency: `${"property"}` instead of `${'property'}`
|
|
392
|
+
4. **Avoid special characters in property names** when possible to keep expressions simple
|
|
@@ -110,6 +110,71 @@ describe("Expression evaluation", () => {
|
|
|
110
110
|
expression.evaluate();
|
|
111
111
|
}).toThrow('Unexpected function call "czm_住所"');
|
|
112
112
|
});
|
|
113
|
+
|
|
114
|
+
test("should evaluate expression with quoted property names containing spaces", () => {
|
|
115
|
+
const expressionString = '${"user name"}';
|
|
116
|
+
const feature = {
|
|
117
|
+
properties: {
|
|
118
|
+
"user name": "Alice",
|
|
119
|
+
"user age": 25,
|
|
120
|
+
},
|
|
121
|
+
} as Feature;
|
|
122
|
+
|
|
123
|
+
const expression = new Expression(expressionString, feature);
|
|
124
|
+
const result = expression.evaluate();
|
|
125
|
+
|
|
126
|
+
expect(result).toBe("Alice");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("should evaluate conditional expression with quoted property names", () => {
|
|
130
|
+
const expressionString = '${"user score"} > 50 ? "Pass" : "Fail"';
|
|
131
|
+
const feature1 = {
|
|
132
|
+
properties: {
|
|
133
|
+
"user score": 75,
|
|
134
|
+
},
|
|
135
|
+
} as Feature;
|
|
136
|
+
const feature2 = {
|
|
137
|
+
properties: {
|
|
138
|
+
"user score": 30,
|
|
139
|
+
},
|
|
140
|
+
} as Feature;
|
|
141
|
+
|
|
142
|
+
const expression1 = new Expression(expressionString, feature1);
|
|
143
|
+
const expression2 = new Expression(expressionString, feature2);
|
|
144
|
+
|
|
145
|
+
expect(expression1.evaluate()).toBe("Pass");
|
|
146
|
+
expect(expression2.evaluate()).toBe("Fail");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("should evaluate arithmetic expression with quoted property names", () => {
|
|
150
|
+
const expressionString = '${"item price"} * ${"item quantity"}';
|
|
151
|
+
const feature = {
|
|
152
|
+
properties: {
|
|
153
|
+
"item price": 10.5,
|
|
154
|
+
"item quantity": 3,
|
|
155
|
+
},
|
|
156
|
+
} as Feature;
|
|
157
|
+
|
|
158
|
+
const expression = new Expression(expressionString, feature);
|
|
159
|
+
const result = expression.evaluate();
|
|
160
|
+
|
|
161
|
+
expect(result).toBe(31.5);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("should handle quoted property names with special characters", () => {
|
|
165
|
+
const expressionString = '${"user-info:name"} === "Bob Smith"';
|
|
166
|
+
const feature = {
|
|
167
|
+
properties: {
|
|
168
|
+
"user-info:name": "Bob Smith",
|
|
169
|
+
"email@address": "bob@example.com",
|
|
170
|
+
},
|
|
171
|
+
} as Feature;
|
|
172
|
+
|
|
173
|
+
const expression = new Expression(expressionString, feature);
|
|
174
|
+
const result = expression.evaluate();
|
|
175
|
+
|
|
176
|
+
expect(result).toBe(true);
|
|
177
|
+
});
|
|
113
178
|
});
|
|
114
179
|
|
|
115
180
|
describe("expression caches", () => {
|
|
@@ -51,4 +51,182 @@ describe("replaceVariables", () => {
|
|
|
51
51
|
const [result, _] = replaceVariables("${vari-able}");
|
|
52
52
|
expect(result).toBe(`czm_vari$reearth_hyphen_$able`);
|
|
53
53
|
});
|
|
54
|
+
|
|
55
|
+
test("should handle property names with spaces using bracket notation with double quotes", () => {
|
|
56
|
+
const [, res] = replaceVariables('${$["property name"]}', {
|
|
57
|
+
"property name": "value with space",
|
|
58
|
+
normalProperty: "normal value",
|
|
59
|
+
});
|
|
60
|
+
expect(res[0].literalValue).toBe("value with space");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("should handle property names with spaces using bracket notation with single quotes", () => {
|
|
64
|
+
const [, res] = replaceVariables("${$['user name']}", {
|
|
65
|
+
"user name": "John Doe",
|
|
66
|
+
normalProperty: "normal value",
|
|
67
|
+
});
|
|
68
|
+
expect(res[0].literalValue).toBe("John Doe");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("should handle nested property names with spaces", () => {
|
|
72
|
+
const [, res] = replaceVariables('${$["contact info"]["email address"]}', {
|
|
73
|
+
"contact info": {
|
|
74
|
+
"email address": "john@example.com",
|
|
75
|
+
"phone number": "123-456-7890",
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
expect(res[0].literalValue).toBe("john@example.com");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("should handle array elements with property names containing spaces", () => {
|
|
82
|
+
const [, res] = replaceVariables('${$.items[0]["item name"]}', {
|
|
83
|
+
items: [
|
|
84
|
+
{ "item name": "Product A", "item price": 100 },
|
|
85
|
+
{ "item name": "Product B", "item price": 200 },
|
|
86
|
+
],
|
|
87
|
+
});
|
|
88
|
+
expect(res[0].literalValue).toBe("Product A");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("should handle array slice with property names containing spaces", () => {
|
|
92
|
+
const [, res] = replaceVariables('${$.items[:1]["item price"]}', {
|
|
93
|
+
items: [
|
|
94
|
+
{ "item name": "Product A", "item price": 100 },
|
|
95
|
+
{ "item name": "Product B", "item price": 200 },
|
|
96
|
+
],
|
|
97
|
+
});
|
|
98
|
+
expect(res[0].literalValue).toBe(100);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("should handle quoted dot notation for property names with spaces", () => {
|
|
102
|
+
const [, res] = replaceVariables("${$.'property name'}", {
|
|
103
|
+
"property name": "value with space",
|
|
104
|
+
});
|
|
105
|
+
expect(res[0].literalValue).toBe("value with space");
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("should handle multiple property names with spaces in one expression", () => {
|
|
109
|
+
const [result, res] = replaceVariables('${$["user name"]} - ${$["property name"]}', {
|
|
110
|
+
"user name": "John Doe",
|
|
111
|
+
"property name": "value with space",
|
|
112
|
+
});
|
|
113
|
+
expect(res).toHaveLength(2);
|
|
114
|
+
expect(res[0].literalValue).toBe("John Doe");
|
|
115
|
+
expect(res[1].literalValue).toBe("value with space");
|
|
116
|
+
expect(result).toContain(res[0].literalName);
|
|
117
|
+
expect(result).toContain(res[1].literalName);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("should handle quoted property names with double quotes", () => {
|
|
121
|
+
const [result, res] = replaceVariables('${"user info"}', {
|
|
122
|
+
"user info": "John Doe",
|
|
123
|
+
normalProperty: "normal value",
|
|
124
|
+
});
|
|
125
|
+
expect(res).toHaveLength(1);
|
|
126
|
+
expect(res[0].literalValue).toBe("John Doe");
|
|
127
|
+
expect(result).toBe(res[0].literalName);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("should handle quoted property names with single quotes", () => {
|
|
131
|
+
const [result, res] = replaceVariables("${'property name'}", {
|
|
132
|
+
"property name": "value with space",
|
|
133
|
+
normalProperty: "normal value",
|
|
134
|
+
});
|
|
135
|
+
expect(res).toHaveLength(1);
|
|
136
|
+
expect(res[0].literalValue).toBe("value with space");
|
|
137
|
+
expect(result).toBe(res[0].literalName);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("should handle multiple quoted property names in one expression", () => {
|
|
141
|
+
const [result, res] = replaceVariables('${"user name"} - ${"user age"}', {
|
|
142
|
+
"user name": "John Doe",
|
|
143
|
+
"user age": 30,
|
|
144
|
+
});
|
|
145
|
+
expect(res).toHaveLength(2);
|
|
146
|
+
expect(res[0].literalValue).toBe("John Doe");
|
|
147
|
+
expect(res[1].literalValue).toBe(30);
|
|
148
|
+
expect(result).toContain(res[0].literalName);
|
|
149
|
+
expect(result).toContain("-");
|
|
150
|
+
expect(result).toContain(res[1].literalName);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("should handle quoted property names with special characters", () => {
|
|
154
|
+
const [result, res] = replaceVariables('${"user-info:name"}', {
|
|
155
|
+
"user-info:name": "Jane Smith",
|
|
156
|
+
});
|
|
157
|
+
expect(res).toHaveLength(1);
|
|
158
|
+
expect(res[0].literalValue).toBe("Jane Smith");
|
|
159
|
+
expect(result).toBe(res[0].literalName);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("should reject mismatched quote types (double to single)", () => {
|
|
163
|
+
const [result, res] = replaceVariables("${\"user info'}", {
|
|
164
|
+
"user info": "John Doe",
|
|
165
|
+
});
|
|
166
|
+
// Should not match the quoted pattern, should fall back to variable name
|
|
167
|
+
expect(result).toContain("czm_");
|
|
168
|
+
expect(res).toHaveLength(0);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("should reject mismatched quote types (single to double)", () => {
|
|
172
|
+
const [result, res] = replaceVariables("${'user info\"}", {
|
|
173
|
+
"user info": "Jane Doe",
|
|
174
|
+
});
|
|
175
|
+
// Should not match the quoted pattern, should fall back to variable name
|
|
176
|
+
expect(result).toContain("czm_");
|
|
177
|
+
expect(res).toHaveLength(0);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("should correctly handle consecutive properties with different quote types", () => {
|
|
181
|
+
const [result, res] = replaceVariables("${\"prop1\"} + ${'prop2'}", {
|
|
182
|
+
prop1: "value1",
|
|
183
|
+
prop2: "value2",
|
|
184
|
+
});
|
|
185
|
+
expect(res).toHaveLength(2);
|
|
186
|
+
expect(res[0].literalValue).toBe("value1");
|
|
187
|
+
expect(res[1].literalValue).toBe("value2");
|
|
188
|
+
expect(result).toContain(res[0].literalName);
|
|
189
|
+
expect(result).toContain("+");
|
|
190
|
+
expect(result).toContain(res[1].literalName);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("should return empty string when quoted property is missing (consistent with regular variables)", () => {
|
|
194
|
+
const [result, res] = replaceVariables('${"missing"}', {
|
|
195
|
+
existing: "value",
|
|
196
|
+
});
|
|
197
|
+
// Returns empty string for missing quoted property (consistent with regular variables)
|
|
198
|
+
expect(res).toHaveLength(1);
|
|
199
|
+
expect(res[0].literalValue).toBe("");
|
|
200
|
+
expect(result).toBe(res[0].literalName);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("should pass through regular variable name when property might be missing", () => {
|
|
204
|
+
const [result, res] = replaceVariables("${missing}");
|
|
205
|
+
// Regular variables are passed through as czm_variableName
|
|
206
|
+
// They will be evaluated later by Node._evaluateVariable
|
|
207
|
+
expect(result).toBe("czm_missing");
|
|
208
|
+
expect(res).toHaveLength(0);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test("should return empty string for missing JSONPath properties (consistent with regular variables)", () => {
|
|
212
|
+
const [result, res] = replaceVariables("${$.missingPath}", {
|
|
213
|
+
existing: "value",
|
|
214
|
+
});
|
|
215
|
+
// Returns empty string for missing JSONPath property
|
|
216
|
+
expect(res).toHaveLength(1);
|
|
217
|
+
expect(res[0].literalValue).toBe("");
|
|
218
|
+
expect(result).toBe(res[0].literalName);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test("should handle mixed existing and missing properties consistently", () => {
|
|
222
|
+
const [result, res] = replaceVariables('${"existing"} - ${"missing"}', {
|
|
223
|
+
existing: "value",
|
|
224
|
+
});
|
|
225
|
+
expect(res).toHaveLength(2);
|
|
226
|
+
expect(res[0].literalValue).toBe("value");
|
|
227
|
+
expect(res[1].literalValue).toBe(""); // Missing property returns empty string
|
|
228
|
+
expect(result).toContain(res[0].literalName);
|
|
229
|
+
expect(result).toContain("-");
|
|
230
|
+
expect(result).toContain(res[1].literalName);
|
|
231
|
+
});
|
|
54
232
|
});
|