@fincity/kirun-js 2.16.2 → 3.0.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.
@@ -0,0 +1,384 @@
1
+ import { MapUtil, TokenValueExtractor } from '../../../../src';
2
+ import { ExpressionEvaluator } from '../../../../src/engine/runtime/expression/ExpressionEvaluator';
3
+
4
+ class TestTokenValueExtractor extends TokenValueExtractor {
5
+ private readonly store: any;
6
+
7
+ constructor(store: any) {
8
+ super();
9
+ this.store = store;
10
+ }
11
+
12
+ protected getValueInternal(token: string): any {
13
+ return this.retrieveElementFrom(token, token.split('.'), 1, this.store);
14
+ }
15
+
16
+ public getPrefix(): string {
17
+ return 'Page.';
18
+ }
19
+
20
+ public getStore(): any {
21
+ return this.store;
22
+ }
23
+ }
24
+
25
+ describe('Expression Evaluator - Length Subtraction Bug Reproduction', () => {
26
+ /**
27
+ * These tests reproduce the bug where:
28
+ * Page.mainFilterCondition.condition.conditions[0].conditions.length-1
29
+ * fails with: "Cannot evaluate expression [object Object] - 1"
30
+ *
31
+ * The issue occurs when accessing .length on an object/array and then subtracting.
32
+ */
33
+
34
+ describe('Bug reproduction - nested array length subtraction', () => {
35
+ test('nested array access with length-1 (exact failing case from bug report)', () => {
36
+ const ttv = new TestTokenValueExtractor({
37
+ mainFilterCondition: {
38
+ condition: {
39
+ conditions: [
40
+ {
41
+ conditions: [1, 2, 3, 4],
42
+ },
43
+ {
44
+ conditions: [5, 6],
45
+ },
46
+ ],
47
+ },
48
+ },
49
+ });
50
+
51
+ // This is the exact expression from the bug report
52
+ // NOTE: This test currently PASSES, suggesting the bug may be data-dependent
53
+ // or related to specific object structures with length properties
54
+ const ev = new ExpressionEvaluator(
55
+ 'Page.mainFilterCondition.condition.conditions[0].conditions.length-1',
56
+ );
57
+
58
+ // Expected result: 3 (array length 4 - 1)
59
+ const result = ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
60
+ expect(result).toBe(3);
61
+ });
62
+
63
+ test('nested array access with length - 1 (with spaces)', () => {
64
+ const ttv = new TestTokenValueExtractor({
65
+ mainFilterCondition: {
66
+ condition: {
67
+ conditions: [
68
+ {
69
+ conditions: [1, 2, 3, 4],
70
+ },
71
+ ],
72
+ },
73
+ },
74
+ });
75
+
76
+ const ev = new ExpressionEvaluator(
77
+ 'Page.mainFilterCondition.condition.conditions[0].conditions.length - 1',
78
+ );
79
+
80
+ expect(() => {
81
+ ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
82
+ }).not.toThrow();
83
+
84
+ const result = ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
85
+ expect(result).toBe(3);
86
+ });
87
+ });
88
+
89
+ describe('Edge cases - object with length property', () => {
90
+ test('FIXED: object with length property that is an object should return Object.keys length', () => {
91
+ const ttv = new TestTokenValueExtractor({
92
+ obj: {
93
+ length: { nested: 'object' }, // Object has a length property
94
+ },
95
+ });
96
+
97
+ // When accessing obj.length, it should return Object.keys(obj).length
98
+ // (ignoring the length property on the object)
99
+ // Then obj.length - 1 should work correctly
100
+ const ev = new ExpressionEvaluator('Page.obj.length-1');
101
+
102
+ // Should return Object.keys(obj).length - 1 = 1 - 1 = 0
103
+ // (obj has one key: 'length')
104
+ const result = ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
105
+ expect(result).toBe(0);
106
+ });
107
+
108
+ test('Nested object with length property in path', () => {
109
+ // This might be closer to the actual bug scenario
110
+ const ttv = new TestTokenValueExtractor({
111
+ mainFilterCondition: {
112
+ condition: {
113
+ conditions: [
114
+ {
115
+ conditions: [1, 2, 3, 4],
116
+ length: { some: 'object' }, // Object has length property
117
+ },
118
+ ],
119
+ },
120
+ },
121
+ });
122
+
123
+ // If conditions[0] has a length property, accessing conditions.length might return that
124
+ const ev = new ExpressionEvaluator(
125
+ 'Page.mainFilterCondition.condition.conditions[0].conditions.length-1',
126
+ );
127
+
128
+ // This might fail if the parser gets confused
129
+ // The issue: conditions[0] has a length property, but we want conditions.length (array length)
130
+ expect(() => {
131
+ ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
132
+ }).not.toThrow();
133
+
134
+ // Should still return 3 (array length 4 - 1), not affected by the length property on the parent
135
+ const result = ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
136
+ expect(result).toBe(3);
137
+ });
138
+
139
+ test('object with length property that is a number should work', () => {
140
+ const ttv = new TestTokenValueExtractor({
141
+ obj: {
142
+ length: 5, // length property exists and is a number
143
+ },
144
+ });
145
+
146
+ // When obj has a length property, it should return that value
147
+ const ev = new ExpressionEvaluator('Page.obj.length-1');
148
+ const result = ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
149
+
150
+ // Should return 5 - 1 = 4 (the length property value minus 1)
151
+ expect(result).toBe(4);
152
+ });
153
+
154
+ test('array length should always work (no length property conflict)', () => {
155
+ const ttv = new TestTokenValueExtractor({
156
+ arr: [1, 2, 3, 4, 5],
157
+ });
158
+
159
+ const ev = new ExpressionEvaluator('Page.arr.length-1');
160
+ const result = ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
161
+
162
+ // Arrays always have .length, should return 5 - 1 = 4
163
+ expect(result).toBe(4);
164
+ });
165
+ });
166
+
167
+ describe('Complex nested scenarios', () => {
168
+ test('deeply nested structure with multiple array accesses', () => {
169
+ const ttv = new TestTokenValueExtractor({
170
+ level1: {
171
+ level2: {
172
+ level3: {
173
+ items: [
174
+ {
175
+ subItems: ['a', 'b', 'c', 'd', 'e'],
176
+ },
177
+ {
178
+ subItems: ['f', 'g'],
179
+ },
180
+ ],
181
+ },
182
+ },
183
+ },
184
+ });
185
+
186
+ const ev = new ExpressionEvaluator(
187
+ 'Page.level1.level2.level3.items[0].subItems.length-1',
188
+ );
189
+
190
+ expect(() => {
191
+ ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
192
+ }).not.toThrow();
193
+
194
+ const result = ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
195
+ expect(result).toBe(4); // 5 - 1
196
+ });
197
+
198
+ test('FIXED: multiple length operations in same expression should work', () => {
199
+ const ttv = new TestTokenValueExtractor({
200
+ arr1: [1, 2, 3],
201
+ arr2: [4, 5, 6, 7, 8],
202
+ });
203
+
204
+ const ev = new ExpressionEvaluator(
205
+ 'Page.arr1.length-1 + Page.arr2.length-1',
206
+ );
207
+
208
+ // Should work correctly now
209
+ expect(() => {
210
+ ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
211
+ }).not.toThrow();
212
+
213
+ // Expected result: (3-1) + (5-1) = 2 + 4 = 6
214
+ const result = ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
215
+ expect(result).toBe(6);
216
+ });
217
+
218
+ test('length-1 used as array index', () => {
219
+ const ttv = new TestTokenValueExtractor({
220
+ items: [10, 20, 30, 40, 50],
221
+ });
222
+
223
+ // Access last element using length-1
224
+ const ev = new ExpressionEvaluator('Page.items[Page.items.length-1]');
225
+
226
+ expect(() => {
227
+ ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
228
+ }).not.toThrow();
229
+
230
+ const result = ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
231
+ expect(result).toBe(50);
232
+ });
233
+ });
234
+
235
+ describe('Different data types with length', () => {
236
+ test('string length subtraction', () => {
237
+ const ttv = new TestTokenValueExtractor({
238
+ text: 'hello world',
239
+ });
240
+
241
+ const ev = new ExpressionEvaluator('Page.text.length-1');
242
+ const result = ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
243
+
244
+ expect(result).toBe(10); // 11 - 1
245
+ });
246
+
247
+ test('empty array length minus 1', () => {
248
+ const ttv = new TestTokenValueExtractor({
249
+ empty: [],
250
+ });
251
+
252
+ const ev = new ExpressionEvaluator('Page.empty.length-1');
253
+ const result = ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
254
+
255
+ expect(result).toBe(-1); // 0 - 1
256
+ });
257
+
258
+ test('object with no length property (should use Object.keys)', () => {
259
+ const ttv = new TestTokenValueExtractor({
260
+ obj: { a: 1, b: 2, c: 3 },
261
+ });
262
+
263
+ const ev = new ExpressionEvaluator('Page.obj.length-1');
264
+ const result = ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
265
+
266
+ expect(result).toBe(2); // 3 keys - 1
267
+ });
268
+ });
269
+
270
+ describe('Specific bug scenarios', () => {
271
+ test('scenario where intermediate object has length property', () => {
272
+ // This test checks if having a length property on an intermediate object
273
+ // causes issues when accessing array.length further down the path
274
+ const ttv = new TestTokenValueExtractor({
275
+ mainFilterCondition: {
276
+ condition: {
277
+ length: { conflict: true }, // This might cause issues
278
+ conditions: [
279
+ {
280
+ conditions: [1, 2, 3, 4],
281
+ },
282
+ ],
283
+ },
284
+ },
285
+ });
286
+
287
+ // Accessing conditions.length should still work (it's an array)
288
+ const ev = new ExpressionEvaluator(
289
+ 'Page.mainFilterCondition.condition.conditions[0].conditions.length-1',
290
+ );
291
+
292
+ expect(() => {
293
+ ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
294
+ }).not.toThrow();
295
+
296
+ const result = ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
297
+ expect(result).toBe(3);
298
+ });
299
+
300
+ test('scenario where array element has length property', () => {
301
+ // What if one of the array elements has a length property?
302
+ const ttv = new TestTokenValueExtractor({
303
+ mainFilterCondition: {
304
+ condition: {
305
+ conditions: [
306
+ {
307
+ conditions: [1, 2, 3, 4],
308
+ length: { nested: 'value' }, // Array element has length
309
+ },
310
+ ],
311
+ },
312
+ },
313
+ });
314
+
315
+ const ev = new ExpressionEvaluator(
316
+ 'Page.mainFilterCondition.condition.conditions[0].conditions.length-1',
317
+ );
318
+
319
+ // This should still work - we're accessing conditions.length (the array)
320
+ // not the length property of the object
321
+ expect(() => {
322
+ ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
323
+ }).not.toThrow();
324
+
325
+ const result = ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
326
+ expect(result).toBe(3);
327
+ });
328
+ });
329
+
330
+ describe('Expression parsing edge cases', () => {
331
+ test('length-1 without spaces in complex path', () => {
332
+ const ttv = new TestTokenValueExtractor({
333
+ data: {
334
+ nested: {
335
+ list: [1, 2, 3, 4, 5, 6],
336
+ },
337
+ },
338
+ });
339
+
340
+ const ev = new ExpressionEvaluator('Page.data.nested.list.length-1');
341
+
342
+ expect(() => {
343
+ ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
344
+ }).not.toThrow();
345
+
346
+ const result = ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
347
+ expect(result).toBe(5);
348
+ });
349
+
350
+ test('length - 1 with spaces in complex path', () => {
351
+ const ttv = new TestTokenValueExtractor({
352
+ data: {
353
+ nested: {
354
+ list: [1, 2, 3, 4, 5, 6],
355
+ },
356
+ },
357
+ });
358
+
359
+ const ev = new ExpressionEvaluator('Page.data.nested.list.length - 1');
360
+
361
+ expect(() => {
362
+ ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
363
+ }).not.toThrow();
364
+
365
+ const result = ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
366
+ expect(result).toBe(5);
367
+ });
368
+
369
+ test('parentheses around length-1', () => {
370
+ const ttv = new TestTokenValueExtractor({
371
+ items: [1, 2, 3, 4],
372
+ });
373
+
374
+ const ev = new ExpressionEvaluator('(Page.items.length-1)');
375
+
376
+ expect(() => {
377
+ ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
378
+ }).not.toThrow();
379
+
380
+ const result = ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv));
381
+ expect(result).toBe(3);
382
+ });
383
+ });
384
+ });