@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,338 @@
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 - Array length with subtraction (working patterns)', () => {
26
+ test('array.length alone should work', () => {
27
+ const ttv = new TestTokenValueExtractor({
28
+ conditions: [1, 2, 3, 4, 5],
29
+ });
30
+
31
+ const ev = new ExpressionEvaluator('Page.conditions.length');
32
+ expect(ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv))).toBe(5);
33
+ });
34
+
35
+ test('array.length - 1 with spaces should work', () => {
36
+ const ttv = new TestTokenValueExtractor({
37
+ conditions: [1, 2, 3, 4, 5],
38
+ });
39
+
40
+ const ev = new ExpressionEvaluator('Page.conditions.length - 1');
41
+ expect(ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv))).toBe(4);
42
+ });
43
+
44
+ test('array.length-1 without spaces should work', () => {
45
+ const ttv = new TestTokenValueExtractor({
46
+ conditions: [1, 2, 3, 4, 5],
47
+ });
48
+
49
+ const ev = new ExpressionEvaluator('Page.conditions.length-1');
50
+ expect(ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv))).toBe(4);
51
+ });
52
+
53
+ test('nested array access with length-1 without spaces', () => {
54
+ const ttv = new TestTokenValueExtractor({
55
+ mainFilterCondition: {
56
+ condition: {
57
+ conditions: [
58
+ {
59
+ conditions: [1, 2, 3, 4],
60
+ },
61
+ {
62
+ conditions: [5, 6],
63
+ },
64
+ ],
65
+ },
66
+ },
67
+ });
68
+
69
+ // This is the exact expression pattern from the bug report
70
+ const ev = new ExpressionEvaluator(
71
+ 'Page.mainFilterCondition.condition.conditions[0].conditions.length-1',
72
+ );
73
+ expect(ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv))).toBe(3);
74
+ });
75
+
76
+ test('nested array access with length - 1 with spaces', () => {
77
+ const ttv = new TestTokenValueExtractor({
78
+ mainFilterCondition: {
79
+ condition: {
80
+ conditions: [
81
+ {
82
+ conditions: [1, 2, 3, 4],
83
+ },
84
+ {
85
+ conditions: [5, 6],
86
+ },
87
+ ],
88
+ },
89
+ },
90
+ });
91
+
92
+ const ev = new ExpressionEvaluator(
93
+ 'Page.mainFilterCondition.condition.conditions[0].conditions.length - 1',
94
+ );
95
+ expect(ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv))).toBe(3);
96
+ });
97
+
98
+ test('array length with addition', () => {
99
+ const ttv = new TestTokenValueExtractor({
100
+ items: [1, 2, 3],
101
+ });
102
+
103
+ const ev = new ExpressionEvaluator('Page.items.length+2');
104
+ expect(ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv))).toBe(5);
105
+ });
106
+
107
+ test('array length with multiplication', () => {
108
+ const ttv = new TestTokenValueExtractor({
109
+ items: [1, 2, 3],
110
+ });
111
+
112
+ const ev = new ExpressionEvaluator('Page.items.length*2');
113
+ expect(ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv))).toBe(6);
114
+ });
115
+
116
+ test('array length in complex expression', () => {
117
+ const ttv = new TestTokenValueExtractor({
118
+ arr1: [1, 2, 3],
119
+ arr2: [4, 5, 6, 7],
120
+ });
121
+
122
+ // arr1.length + arr2.length - 1 = 3 + 4 - 1 = 6
123
+ const ev = new ExpressionEvaluator('Page.arr1.length + Page.arr2.length - 1');
124
+ expect(ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv))).toBe(6);
125
+ });
126
+
127
+ test('using length-1 as array index', () => {
128
+ const ttv = new TestTokenValueExtractor({
129
+ items: [10, 20, 30, 40, 50],
130
+ });
131
+
132
+ // Access last element: items[items.length - 1]
133
+ const ev = new ExpressionEvaluator('Page.items[Page.items.length - 1]');
134
+ expect(ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv))).toBe(50);
135
+ });
136
+
137
+ test('using length-1 without spaces as array index', () => {
138
+ const ttv = new TestTokenValueExtractor({
139
+ items: [10, 20, 30, 40, 50],
140
+ });
141
+
142
+ // Access last element: items[items.length-1]
143
+ const ev = new ExpressionEvaluator('Page.items[Page.items.length-1]');
144
+ expect(ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv))).toBe(50);
145
+ });
146
+
147
+ test('nested object with array length subtraction', () => {
148
+ const ttv = new TestTokenValueExtractor({
149
+ data: {
150
+ nested: {
151
+ list: ['a', 'b', 'c', 'd'],
152
+ },
153
+ },
154
+ });
155
+
156
+ const ev = new ExpressionEvaluator('Page.data.nested.list.length-1');
157
+ expect(ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv))).toBe(3);
158
+ });
159
+
160
+ test('string length with subtraction', () => {
161
+ const ttv = new TestTokenValueExtractor({
162
+ text: 'hello',
163
+ });
164
+
165
+ const ev = new ExpressionEvaluator('Page.text.length-1');
166
+ expect(ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv))).toBe(4);
167
+ });
168
+
169
+ test('object keys length with subtraction', () => {
170
+ const ttv = new TestTokenValueExtractor({
171
+ obj: { a: 1, b: 2, c: 3, d: 4 },
172
+ });
173
+
174
+ // Object with 4 keys, length should be 4
175
+ const ev = new ExpressionEvaluator('Page.obj.length-1');
176
+ expect(ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv))).toBe(3);
177
+ });
178
+
179
+ test('empty array length minus 1', () => {
180
+ const ttv = new TestTokenValueExtractor({
181
+ empty: [],
182
+ });
183
+
184
+ const ev = new ExpressionEvaluator('Page.empty.length-1');
185
+ expect(ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv))).toBe(-1);
186
+ });
187
+
188
+ test('multiple length operations in same expression', () => {
189
+ const ttv = new TestTokenValueExtractor({
190
+ list1: [1, 2, 3],
191
+ list2: [4, 5],
192
+ });
193
+
194
+ // (list1.length-1) * (list2.length-1) = 2 * 1 = 2
195
+ const ev = new ExpressionEvaluator('(Page.list1.length-1) * (Page.list2.length-1)');
196
+ expect(ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv))).toBe(2);
197
+ });
198
+
199
+ test('length with division', () => {
200
+ const ttv = new TestTokenValueExtractor({
201
+ items: [1, 2, 3, 4, 5, 6],
202
+ });
203
+
204
+ const ev = new ExpressionEvaluator('Page.items.length/2');
205
+ expect(ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv))).toBe(3);
206
+ });
207
+
208
+ test('length with modulus', () => {
209
+ const ttv = new TestTokenValueExtractor({
210
+ items: [1, 2, 3, 4, 5],
211
+ });
212
+
213
+ const ev = new ExpressionEvaluator('Page.items.length%3');
214
+ expect(ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv))).toBe(2);
215
+ });
216
+
217
+ test('simple greater than comparison (no subtraction)', () => {
218
+ const ttv = new TestTokenValueExtractor({
219
+ items: [1, 2, 3],
220
+ });
221
+
222
+ // Page.items.length > 0 = 3 > 0 = true
223
+ const ev = new ExpressionEvaluator('Page.items.length > 0');
224
+ expect(ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv))).toBe(true);
225
+ });
226
+ });
227
+
228
+ describe('Expression Evaluator - Alternative patterns with parentheses', () => {
229
+ // These tests demonstrate alternative patterns using parentheses
230
+ // These patterns work, but parentheses are no longer required (see "Previously known issues" section)
231
+
232
+ test('Alternative: length comparison with parentheses and spaces', () => {
233
+ const ttv = new TestTokenValueExtractor({
234
+ items: [1, 2, 3],
235
+ });
236
+
237
+ // Using parentheses: (Page.items.length - 1) > 0
238
+ const ev = new ExpressionEvaluator('(Page.items.length - 1) > 0');
239
+ expect(ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv))).toBe(true);
240
+ });
241
+
242
+ test('Alternative: length comparison with parentheses no spaces', () => {
243
+ const ttv = new TestTokenValueExtractor({
244
+ items: [1, 2, 3],
245
+ });
246
+
247
+ // Using parentheses: (Page.items.length-1) > 0
248
+ const ev = new ExpressionEvaluator('(Page.items.length-1) > 0');
249
+ expect(ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv))).toBe(true);
250
+ });
251
+
252
+ test('Alternative: literal subtraction and comparison with parentheses', () => {
253
+ const ttv = new TestTokenValueExtractor({});
254
+
255
+ // Using parentheses: (5 - 1) > 0
256
+ const ev = new ExpressionEvaluator('(5 - 1) > 0');
257
+ expect(ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv))).toBe(true);
258
+ });
259
+
260
+ test('Alternative: chained subtraction with parentheses', () => {
261
+ const ttv = new TestTokenValueExtractor({});
262
+
263
+ // Using parentheses: (5 - 1) - 2 = 4 - 2 = 2
264
+ const ev = new ExpressionEvaluator('(5 - 1) - 2');
265
+ expect(ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv))).toBe(2);
266
+ });
267
+
268
+ test('Alternative: length in ternary with parentheses', () => {
269
+ const ttv = new TestTokenValueExtractor({
270
+ items: [1, 2, 3, 4],
271
+ });
272
+
273
+ // Using parentheses: (Page.items.length - 1) > 2 ? 'big' : 'small'
274
+ const ev = new ExpressionEvaluator("(Page.items.length - 1) > 2 ? 'big' : 'small'");
275
+ expect(ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv))).toBe('big');
276
+ });
277
+
278
+ test('Alternative: subtraction with equality using parentheses', () => {
279
+ const ttv = new TestTokenValueExtractor({
280
+ items: [1, 2, 3],
281
+ });
282
+
283
+ // Using parentheses: (Page.items.length - 1) = 2
284
+ const ev = new ExpressionEvaluator('(Page.items.length - 1) = 2');
285
+ expect(ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv))).toBe(true);
286
+ });
287
+ });
288
+
289
+ describe('Expression Evaluator - Previously known issues (now fixed)', () => {
290
+ // These tests document expressions that previously failed without parentheses
291
+ // These issues have been fixed and now work correctly without parentheses
292
+
293
+ test('FIXED: length comparison without parentheses now works', () => {
294
+ const ttv = new TestTokenValueExtractor({
295
+ items: [1, 2, 3],
296
+ });
297
+
298
+ // This now works: Page.items.length - 1 > 0
299
+ const ev = new ExpressionEvaluator('Page.items.length - 1 > 0');
300
+ expect(ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv))).toBe(true);
301
+ });
302
+
303
+ test('FIXED: simple subtraction and comparison without parentheses now works', () => {
304
+ const ttv = new TestTokenValueExtractor({});
305
+
306
+ // This now works: 5 - 1 > 0
307
+ const ev = new ExpressionEvaluator('5 - 1 > 0');
308
+ expect(ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv))).toBe(true);
309
+ });
310
+
311
+ test('FIXED: chained subtraction now has correct associativity', () => {
312
+ const ttv = new TestTokenValueExtractor({});
313
+
314
+ // This now correctly evaluates as (5-1)-2 = 2
315
+ const ev = new ExpressionEvaluator('5 - 1 - 2');
316
+ expect(ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv))).toBe(2);
317
+ });
318
+
319
+ test('FIXED: subtraction then equality without parentheses now works', () => {
320
+ const ttv = new TestTokenValueExtractor({
321
+ items: [1, 2, 3],
322
+ });
323
+
324
+ // This now works: Page.items.length - 1 = 2
325
+ const ev = new ExpressionEvaluator('Page.items.length - 1 = 2');
326
+ expect(ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv))).toBe(true);
327
+ });
328
+
329
+ test('FIXED: length in ternary without parentheses now works', () => {
330
+ const ttv = new TestTokenValueExtractor({
331
+ items: [1, 2, 3, 4],
332
+ });
333
+
334
+ // This now works: Page.items.length - 1 > 2 ? 'big' : 'small'
335
+ const ev = new ExpressionEvaluator("Page.items.length - 1 > 2 ? 'big' : 'small'");
336
+ expect(ev.evaluate(MapUtil.of(ttv.getPrefix(), ttv))).toBe('big');
337
+ });
338
+ });
@@ -2,7 +2,8 @@ import { Expression } from '../../../../src/engine/runtime/expression/Expression
2
2
 
3
3
  test('Expression Test', () => {
4
4
  expect(new Expression('2+3').toString()).toBe('(2+3)');
5
- expect(new Expression('2.234 + 3 * 1.22243').toString()).toBe('((2.234)+(3*(1.22243)))');
5
+ // Updated: new parser produces semantically equivalent format without extra leaf parens
6
+ expect(new Expression('2.234 + 3 * 1.22243').toString()).toBe('(2.234+(3*1.22243))');
6
7
  expect(new Expression('10*11+12*13*14/7').toString()).toBe('((10*11)+(12*(13*(14/7))))');
7
8
  expect(new Expression('34 << 2 = 8 ').toString()).toBe('((34<<2)=8)');
8
9
 
@@ -10,13 +11,14 @@ test('Expression Test', () => {
10
11
  'Context.a[Steps.loop.iteration.index - 1]+ Context.a[Steps.loop.iteration.index - 2]',
11
12
  );
12
13
 
14
+ // Updated: new parser produces semantically equivalent format
13
15
  expect(ex.toString()).toBe(
14
- '((Context.(a[((Steps.(loop.(iteration.index)))-1)))+(Context.(a[((Steps.(loop.(iteration.index)))-2))))',
16
+ '((Context.(a[((Steps.(loop.(iteration.index)))-1)]))+(Context.(a[((Steps.(loop.(iteration.index)))-2)])))',
15
17
  );
16
18
 
17
19
  ex = new Expression('Steps.step1.output.obj.array[Steps.step1.output.obj.num +1]+2');
18
20
  expect(ex.toString()).toBe(
19
- '((Steps.(step1.(output.(obj.(array[((Steps.(step1.(output.(obj.num))))+1))))))+2)',
21
+ '((Steps.(step1.(output.(obj.(array[((Steps.(step1.(output.(obj.num))))+1)])))))+2)',
20
22
  );
21
23
 
22
24
  let arrays: Expression = new Expression(
@@ -26,10 +28,10 @@ test('Expression Test', () => {
26
28
  let deepObjectWithArray: Expression = new Expression('Context.a.b[2].c');
27
29
 
28
30
  expect(arrays.toString()).toBe(
29
- '(Context.(a[((Steps.(loop.(iteration.index)))[((Steps.(loop.(iteration.index)))+1))))',
31
+ '(Context.(a[(Steps.(loop.(iteration.index)))][((Steps.(loop.(iteration.index)))+1)]))',
30
32
  );
31
33
  expect(deepObject.toString()).toBe('(Context.(a.(b.c)))');
32
- expect(deepObjectWithArray.toString()).toBe('(Context.(a.(b[(2.c))))');
34
+ expect(deepObjectWithArray.toString()).toBe('(Context.(a.(b[2].c)))');
33
35
 
34
36
  let opInTheName = new Expression('Store.a.b.c or Store.c.d.x');
35
37
  expect(opInTheName.toString()).toBe('((Store.(a.(b.c)))or(Store.(c.(d.x))))');