2 |
3 | {{ prefix }}
4 | {{ computedValue }}
5 | {{ suffix }}
6 |
7 |
16 | {{ errorMsg }}
17 |
18 |
19 |
145 |
--------------------------------------------------------------------------------
/src/operations.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, test, jest } from '@jest/globals';
2 | import { parseExpression, parseOp, toSlug } from './operations';
3 |
4 | jest
5 | .useFakeTimers()
6 | .setSystemTime(new Date('2023-01-01'));
7 |
8 | describe('Test parseExpression', () => {
9 | describe('Dynamic variables', () => {
10 | test('$NOW', () => {
11 | expect(parseExpression('$NOW', {})).toStrictEqual(new Date());
12 | });
13 |
14 | test('$CURRENT_USER', () => {
15 | const user = {
16 | id: 1,
17 | name: 'Duy',
18 | role: {
19 | name: 'admin',
20 | },
21 | };
22 | expect(parseExpression('$CURRENT_USER', {})).toBe(undefined);
23 | expect(parseExpression('$CURRENT_USER', { __currentUser: user })).toBe(1);
24 | expect(parseExpression('$CURRENT_USER.name', { __currentUser: user })).toBe('Duy');
25 | expect(parseExpression('$CURRENT_USER.role.name', { __currentUser: user })).toBe('admin');
26 | expect(parseExpression('$CURRENT_USER.something', { __currentUser: user })).toBe(null);
27 | });
28 | });
29 |
30 | describe('Type conversion ops', () => {
31 | test('INT op', () => {
32 | expect(parseExpression('INT(a)', { a: '1' })).toBe(1);
33 | });
34 |
35 | test('FLOAT op', () => {
36 | expect(parseExpression('FLOAT(a)', { a: '1.234' })).toBe(1.234);
37 | });
38 |
39 | test('STRING op', () => {
40 | expect(parseExpression('STRING(1)', {})).toBe('1');
41 | expect(parseExpression('STRING(a)', { a: 123 })).toBe('123');
42 | });
43 |
44 | test('DATE op', () => {
45 | expect(parseExpression('DATE(a)', { a: '2022-01-01' })).toEqual(new Date('2022-01-01'));
46 | expect(parseExpression('DATE(a)', { a: 1640995200000 })).toEqual(new Date('2022-01-01'));
47 | });
48 |
49 | test('CURRENCY op', () => {
50 | expect(parseExpression('CURRENCY(a)', { a: 1000 })).toBe('1,000');
51 | });
52 | });
53 |
54 | describe('Date ops', () => {
55 | test('DATE_ISO op', () => {
56 | expect(parseExpression('DATE_ISO(a)', { a: '2022-01-01' })).toBe('2022-01-01T00:00:00.000Z');
57 | expect(parseExpression('DATE_ISO($NOW)', {})).toBe('2023-01-01T00:00:00.000Z');
58 | });
59 |
60 | test('DATE_UTC op', () => {
61 | expect(parseExpression('DATE_UTC(a)', { a: '2022-01-01' })).toBe('Sat, 01 Jan 2022 00:00:00 GMT');
62 | expect(parseExpression('DATE_UTC($NOW)', {})).toBe('Sun, 01 Jan 2023 00:00:00 GMT');
63 | });
64 |
65 | test('DATE_STR op', () => {
66 | expect(parseExpression('DATE_STR(a)', { a: new Date('2022-12-31') })).toBe('2022-12-31');
67 | expect(parseExpression('DATE_STR($NOW)', {})).toBe('2023-01-01');
68 | });
69 |
70 | test('TIME_STR op', () => {
71 | expect(parseExpression('TIME_STR(a)', { a: new Date('2022-12-31T11:59:59') })).toBe('11:59:59');
72 | expect(parseExpression('TIME_STR($NOW)', {})).toBe('00:00:00');
73 | });
74 |
75 | test('YEAR op', () => {
76 | expect(parseExpression('YEAR($NOW)', {})).toBe(new Date().getFullYear());
77 | });
78 |
79 | test('MONTH op', () => {
80 | expect(parseExpression('MONTH($NOW)', {})).toBe(new Date().getMonth());
81 | });
82 |
83 | test('GET_DATE op', () => {
84 | expect(parseExpression('GET_DATE($NOW)', {})).toBe(new Date().getDate());
85 | });
86 |
87 | test('DAY op', () => {
88 | expect(parseExpression('DAY($NOW)', {})).toBe(new Date().getDay());
89 | });
90 |
91 | test('HOURS op', () => {
92 | expect(parseExpression('HOURS($NOW)', {})).toBe(new Date().getHours());
93 | });
94 |
95 | test('MINUTES op', () => {
96 | expect(parseExpression('MINUTES($NOW)', {})).toBe(new Date().getMinutes());
97 | });
98 |
99 | test('SECONDS op', () => {
100 | expect(parseExpression('SECONDS($NOW)', {})).toBe(new Date().getSeconds());
101 | });
102 |
103 | test('TIME op', () => {
104 | expect(parseExpression('TIME($NOW)', {})).toBe(new Date().getTime());
105 | });
106 |
107 | test('LOCALE_STR op', () => {
108 | expect(parseExpression('LOCALE_STR($NOW, "en-US", "{}")', {})).toBe('1/1/2023, 12:00:00 AM');
109 | expect(parseExpression('LOCALE_STR($NOW, "en-US", "{\\"month\\": \\"long\\"}")', {})).toBe('January');
110 | expect(parseExpression('LOCALE_STR($NOW, "en-US", "{\\"weekday\\": \\"long\\", \\"year\\": \\"numeric\\", \\"month\\": \\"long\\", \\"day\\": \\"numeric\\"}")', {})).toBe('Sunday, January 1, 2023');
111 | });
112 | });
113 |
114 | describe('Arithmetic ops', () => {
115 | test('ABS op', () => {
116 | expect(parseExpression('ABS(a)', { a: -1 })).toBe(1);
117 | });
118 |
119 | test('SQRT op', () => {
120 | expect(parseExpression('SQRT(a)', { a: 100 })).toBe(10);
121 | });
122 |
123 | test('SUM op', () => {
124 | expect(parseExpression('SUM(a)', { a: [1, 2, 3, 4, 5] })).toBe(15);
125 | expect(parseExpression('SUM(a)', { a: 1 })).toBe(0);
126 | });
127 |
128 | test('AVERAGE op', () => {
129 | expect(parseExpression('AVERAGE(a)', { a: [1, 2, 3, 4, 5] })).toBe(3);
130 | expect(parseExpression('AVERAGE(a)', { a: 1 })).toBe(0);
131 | });
132 |
133 | test('CEIL op', () => {
134 | expect(parseExpression('CEIL(a)', { a: 1.234 })).toBe(2);
135 | });
136 |
137 | test('FLOOR op', () => {
138 | expect(parseExpression('FLOOR(a)', { a: 1.234 })).toBe(1);
139 | });
140 |
141 | test('ROUND op', () => {
142 | expect(parseExpression('ROUND(a)', { a: 1.234 })).toBe(1);
143 | expect(parseExpression('ROUND(a, b)', { a: 5, b: 2 })).toBe('5.00');
144 | });
145 |
146 | test('EXP op', () => {
147 | expect(parseExpression('EXP(a)', { a: 1 })).toBeCloseTo(Math.exp(1), 8);
148 | });
149 |
150 | test('LOG op', () => {
151 | expect(parseExpression('LOG(a)', { a: 10 })).toBeCloseTo(Math.log(10), 8);
152 | });
153 |
154 | test('MAX op', () => {
155 | expect(parseExpression('MAX(a)', { a: [1, 2, 30, 4, 5] })).toBe(30);
156 | expect(parseExpression('MAX(a)', { a: 1 })).toBe(0);
157 | expect(parseExpression('MAX(a, b)', { a: 5, b: 2 })).toBe(5);
158 | });
159 |
160 | test('MIN op', () => {
161 | expect(parseExpression('MIN(a)', { a: [1, 2, 3, -4, 5] })).toBe(-4);
162 | expect(parseExpression('MIN(a)', { a: 1 })).toBe(0);
163 | expect(parseExpression('MIN(a, b)', { a: 5, b: 2 })).toBe(2);
164 | });
165 |
166 | test('SUM op', () => {
167 | expect(parseExpression('SUM(a, b)', { a: 1, b: 2 })).toBe(3);
168 | });
169 |
170 | test('SUBTRACT op', () => {
171 | expect(parseExpression('SUBTRACT(a, b)', { a: 5, b: 2 })).toBe(3);
172 | expect(parseExpression('SUBTRACT(DATE(a), DATE(b))', { a: '2022-01-02', b: '2022-01-01' })).toBe(86400000);
173 | });
174 |
175 | test('MULTIPLY op', () => {
176 | expect(parseExpression('MULTIPLY(a, b)', { a: 5, b: 2 })).toBe(10);
177 | });
178 |
179 | test('DIVIDE op', () => {
180 | expect(parseExpression('DIVIDE(a, b)', { a: 5, b: 2 })).toBe(2.5);
181 | });
182 |
183 | test('REMAINDER op', () => {
184 | expect(parseExpression('REMAINDER(a, b)', { a: 5, b: 2 })).toBe(1);
185 | });
186 |
187 | test('POWER op', () => {
188 | expect(parseExpression('POWER(a, b)', { a: 5, b: 2 })).toBe(25);
189 | });
190 | });
191 |
192 | describe('Boolean ops', () => {
193 | test('NULL op', () => {
194 | expect(parseExpression('NULL(a)', { a: null })).toBe(true);
195 | expect(parseExpression('NULL(a)', { a: undefined })).toBe(false);
196 | expect(parseExpression('NULL(a)', { a: 0 })).toBe(false);
197 | expect(parseExpression('NULL(a)', { a: '' })).toBe(false);
198 | expect(parseExpression('NULL(a)', { a: {} })).toBe(false);
199 | expect(parseExpression('NULL(a)', { a: [] })).toBe(false);
200 | });
201 |
202 | test('NOT_NULL op', () => {
203 | expect(parseExpression('NOT_NULL(a)', { a: null })).toBe(false);
204 | expect(parseExpression('NOT_NULL(a)', { a: undefined })).toBe(true);
205 | expect(parseExpression('NOT_NULL(a)', { a: 0 })).toBe(true);
206 | expect(parseExpression('NOT_NULL(a)', { a: '' })).toBe(true);
207 | expect(parseExpression('NOT_NULL(a)', { a: {} })).toBe(true);
208 | expect(parseExpression('NOT_NULL(a)', { a: [] })).toBe(true);
209 | });
210 |
211 | test('NOT op', () => {
212 | expect(parseExpression('NOT(a)', { a: false })).toBe(true);
213 | expect(parseExpression('NOT(a)', { a: true })).toBe(false);
214 | });
215 |
216 | test('EQUAL op', () => {
217 | expect(parseExpression('EQUAL(a, b)', { a: 1, b: 1 })).toBe(true);
218 | expect(parseExpression('EQUAL(a, b)', { a: 1, b: '1' })).toBe(false);
219 | });
220 |
221 | test('NOT_EQUAL op', () => {
222 | expect(parseExpression('NOT_EQUAL(a, b)', { a: 1, b: 1 })).toBe(false);
223 | expect(parseExpression('NOT_EQUAL(a, b)', { a: 1, b: '1' })).toBe(true);
224 | });
225 |
226 | test('GT op', () => {
227 | expect(parseExpression('GT(a, b)', { a: 1, b: 2 })).toBe(false);
228 | expect(parseExpression('GT(a, b)', { a: 1, b: 1 })).toBe(false);
229 | expect(parseExpression('GT(a, b)', { a: 2, b: 1 })).toBe(true);
230 | });
231 |
232 | test('GTE op', () => {
233 | expect(parseExpression('GTE(a, b)', { a: 1, b: 2 })).toBe(false);
234 | expect(parseExpression('GTE(a, b)', { a: 1, b: 1 })).toBe(true);
235 | expect(parseExpression('GTE(a, b)', { a: 2, b: 1 })).toBe(true);
236 | });
237 |
238 | test('LT op', () => {
239 | expect(parseExpression('LT(a, b)', { a: 1, b: 2 })).toBe(true);
240 | expect(parseExpression('LT(a, b)', { a: 1, b: 1 })).toBe(false);
241 | expect(parseExpression('LT(a, b)', { a: 2, b: 1 })).toBe(false);
242 | });
243 |
244 | test('LTE op', () => {
245 | expect(parseExpression('LTE(a, b)', { a: 1, b: 2 })).toBe(true);
246 | expect(parseExpression('LTE(a, b)', { a: 1, b: 1 })).toBe(true);
247 | expect(parseExpression('LTE(a, b)', { a: 2, b: 1 })).toBe(false);
248 | });
249 |
250 | test('AND op', () => {
251 | expect(parseExpression('AND(a, b)', { a: true, b: true })).toBe(true);
252 | expect(parseExpression('AND(a, b)', { a: true, b: false })).toBe(false);
253 | expect(parseExpression('AND(a, b)', { a: false, b: true })).toBe(false);
254 | expect(parseExpression('AND(a, b)', { a: false, b: false })).toBe(false);
255 | });
256 |
257 | test('OR op', () => {
258 | expect(parseExpression('OR(a, b)', { a: true, b: true })).toBe(true);
259 | expect(parseExpression('OR(a, b)', { a: true, b: false })).toBe(true);
260 | expect(parseExpression('OR(a, b)', { a: false, b: true })).toBe(true);
261 | expect(parseExpression('OR(a, b)', { a: false, b: false })).toBe(false);
262 | });
263 | });
264 |
265 | describe('String ops', () => {
266 | test('STR_LEN op', () => {
267 | expect(parseExpression('STR_LEN(a)', { a: '123' })).toBe(3);
268 | expect(parseExpression('STR_LEN(a)', { a: 1 })).toBe(1);
269 | });
270 |
271 | test('LENGTH op', () => {
272 | expect(parseExpression('LENGTH(a)', { a: '123' })).toBe(3);
273 | expect(parseExpression('LENGTH(a)', { a: 1 })).toBe(null);
274 | });
275 |
276 | test('FIRST op', () => {
277 | expect(parseExpression('FIRST(a)', { a: '123' })).toBe('1');
278 | expect(parseExpression('FIRST(a)', { a: 1 })).toBe(null);
279 | });
280 |
281 | test('LAST op', () => {
282 | expect(parseExpression('LAST(a)', { a: '123' })).toBe('3');
283 | expect(parseExpression('LAST(a)', { a: 1 })).toBe(null);
284 | });
285 |
286 | test('REVERSE op', () => {
287 | expect(parseExpression('REVERSE(a)', { a: '123' })).toBe('321');
288 | expect(parseExpression('REVERSE(a)', { a: 1 })).toBe(null);
289 | });
290 |
291 | test('LOWER op', () => {
292 | expect(parseExpression('LOWER(a)', { a: 'ABCDEF' })).toBe('abcdef');
293 | });
294 |
295 | test('UPPER op', () => {
296 | expect(parseExpression('UPPER(a)', { a: 'abcdef' })).toBe('ABCDEF');
297 | });
298 |
299 | test('TRIM op', () => {
300 | expect(parseExpression('TRIM(a)', { a: ' abc def ' })).toBe('abc def');
301 | });
302 |
303 | test('ENCODE_URL_COMPONENT op', () => {
304 | expect(parseExpression('ENCODE_URL_COMPONENT(a)', { a: 'abc def' })).toBe('abc%20def');
305 | });
306 |
307 | test('CONCAT op', () => {
308 | expect(parseExpression('CONCAT(a, b)', { a: '123', b: '456' })).toBe('123456');
309 | });
310 |
311 | test('LEFT op', () => {
312 | expect(parseExpression('LEFT(a, b)', { a: '123456', b: 2 })).toBe('12');
313 | });
314 |
315 | test('RIGHT op', () => {
316 | expect(parseExpression('RIGHT(a, b)', { a: '123456', b: 2 })).toBe('56');
317 | });
318 |
319 | test('MID op', () => {
320 | expect(parseExpression('MID(a, b, c)', { a: '123456', b: 1, c: 2 })).toBe('23');
321 | });
322 |
323 | test('SLUG op', () => {
324 | expect(parseExpression('SLUG(a)', { a: 'This is a title 123 !@#,./"' })).toBe('this-is-a-title-123-');
325 | });
326 |
327 | test('REPT op', () => {
328 | expect(parseExpression('REPT(a, b)', { a: '123', b: 3 })).toBe('123123123');
329 | });
330 |
331 | test('JOIN op', () => {
332 | expect(parseExpression('JOIN(a, " - ")', { a: ['a', 'b', 'c'] })).toBe('a - b - c');
333 | });
334 |
335 | test('SPLIT op', () => {
336 | expect(parseExpression('SPLIT(a, " - ")', { a: 'a - b - c' })).toEqual(['a', 'b', 'c']);
337 | });
338 |
339 | test('SUBSTITUTE op', () => {
340 | expect(parseExpression('SUBSTITUTE(a, "a", "b")', { a: 'abcabc' })).toBe('bbcbbc');
341 | expect(parseExpression('SUBSTITUTE(a, "d", "b")', { a: 'abcabc' })).toBe('abcabc');
342 | });
343 |
344 | test('SEARCH op', () => {
345 | expect(parseExpression('SEARCH(a, "b")', { a: 'abcabc' })).toBe(1);
346 | expect(parseExpression('SEARCH(a, "b", 3)', { a: 'abcabc' })).toBe(4);
347 | expect(parseExpression('SEARCH(a, "d")', { a: 'abcabc' })).toBe(-1);
348 | });
349 |
350 | test('AT op', () => {
351 | expect(parseExpression('AT(a, 1)', { a: 'abc' })).toBe('b');
352 | expect(parseExpression('AT(a, 1)', { a: 1 })).toBe(null);
353 | });
354 |
355 | test('INDEX_OF op', () => {
356 | expect(parseExpression('INDEX_OF(a, "b")', { a: 'abcabc' })).toBe(1);
357 | expect(parseExpression('INDEX_OF(a, "c")', { a: 'abcabc' })).toBe(2);
358 | expect(parseExpression('INDEX_OF(a, "d")', { a: 'abcabc' })).toBe(-1);
359 | expect(parseExpression('INDEX_OF(a, "b")', { a: 1 })).toBe(null);
360 | });
361 |
362 | test('INCLUDES op', () => {
363 | expect(parseExpression('INCLUDES(a, "b")', { a: 'abcabc' })).toBe(true);
364 | expect(parseExpression('INCLUDES(a, "d")', { a: 'abcabc' })).toBe(false);
365 | expect(parseExpression('INCLUDES(a, "b")', { a: 1 })).toBe(null);
366 | });
367 |
368 | test('SLICE op', () => {
369 | expect(parseExpression('SLICE(a, 1, 2)', { a: 'abcdef' })).toBe('b');
370 | expect(parseExpression('SLICE(a, 1, -1)', { a: 'abcdef' })).toBe('bcde');
371 | });
372 | });
373 |
374 | describe('Array ops', () => {
375 | test('ARRAY_LEN op', () => {
376 | expect(parseExpression('ARRAY_LEN(a)', { a: [1, 2, 3] })).toBe(3);
377 | expect(parseExpression('ARRAY_LEN(a)', { a: 1 })).toBe(0);
378 | });
379 |
380 | test('LENGTH op', () => {
381 | expect(parseExpression('LENGTH(a)', { a: [1, 2, 3] })).toBe(3);
382 | expect(parseExpression('LENGTH(a)', { a: 1 })).toBe(null);
383 | });
384 |
385 | test('FIRST op', () => {
386 | expect(parseExpression('FIRST(a)', { a: [1, 2, 3] })).toBe(1);
387 | expect(parseExpression('FIRST(a)', { a: 1 })).toBe(null);
388 | });
389 |
390 | test('LAST op', () => {
391 | expect(parseExpression('LAST(a)', { a: [1, 2, 3] })).toBe(3);
392 | expect(parseExpression('LAST(a)', { a: 1 })).toBe(null);
393 | });
394 |
395 | test('REVERSE op', () => {
396 | expect(parseExpression('REVERSE(a)', { a: [1, 2, 3] })).toEqual([3, 2, 1]);
397 | expect(parseExpression('REVERSE(a)', { a: 1 })).toBe(null);
398 | });
399 |
400 | test('CONCAT op', () => {
401 | expect(parseExpression('CONCAT(a, b)', { a: [1, 2], b: [3, 4] })).toEqual([1, 2, 3, 4]);
402 | expect(parseExpression('CONCAT(a, b)', { a: [1, 2], b: 3 })).toEqual([1, 2, 3]);
403 | });
404 |
405 | test('AT op', () => {
406 | expect(parseExpression('AT(a, 1)', { a: [1, 2, 3] })).toBe(2);
407 | expect(parseExpression('AT(a, 1)', { a: 1 })).toBe(null);
408 | });
409 |
410 | test('INDEX_OF op', () => {
411 | expect(parseExpression('INDEX_OF(a, 1)', { a: [1, 2, 3] })).toBe(0);
412 | expect(parseExpression('INDEX_OF(a, 2)', { a: [1, 2, 3] })).toBe(1);
413 | expect(parseExpression('INDEX_OF(a, 3)', { a: [1, 2, 3] })).toBe(2);
414 | expect(parseExpression('INDEX_OF(a, 4)', { a: [1, 2, 3] })).toBe(-1);
415 | expect(parseExpression('INDEX_OF(a, 2)', { a: 1 })).toBe(null);
416 | });
417 |
418 | test('INCLUDES op', () => {
419 | expect(parseExpression('INCLUDES(a, 1)', { a: [1, 2, 3] })).toBe(true);
420 | expect(parseExpression('INCLUDES(a, 4)', { a: [1, 2, 3] })).toBe(false);
421 | expect(parseExpression('INCLUDES(a, 2)', { a: 1 })).toBe(null);
422 | });
423 |
424 | test('SLICE op', () => {
425 | expect(parseExpression('SLICE(a, 1, 2)', { a: [1, 2, 3, 4] })).toEqual([2]);
426 | expect(parseExpression('SLICE(a, 1, -1)', { a: [1, 2, 3, 4] })).toEqual([2, 3]);
427 | });
428 |
429 | test('MAP op', () => {
430 | const arr = [
431 | {
432 | a: 1,
433 | b: 'x',
434 | },
435 | {
436 | a: 2,
437 | b: 'y',
438 | },
439 | {
440 | a: 3,
441 | b: 'z',
442 | },
443 | ];
444 | expect(parseExpression('MAP(arr, SUM(a, 1))', { arr })).toEqual([2, 3, 4]);
445 | expect(parseExpression('MAP(arr, CONCAT(b, "a"))', { arr })).toEqual(['xa', 'ya', 'za']);
446 | expect(parseExpression('MAP(arr, REPT(b, a))', { arr })).toEqual(['x', 'yy', 'zzz']);
447 |
448 | const arr2 = [
449 | {
450 | a: {
451 | b: 1,
452 | },
453 | c: ['x'],
454 | },
455 | {
456 | a: {
457 | b: 2,
458 | },
459 | c: ['y'],
460 | },
461 | {
462 | a: {
463 | b: 3,
464 | },
465 | c: ['z'],
466 | },
467 | ];
468 |
469 | expect(parseExpression('MAP(arr2, SUM(a.b, 1))', { arr2 })).toEqual([2, 3, 4]);
470 | expect(parseExpression('MAP(arr2, CONCAT(FIRST(c), "a"))', { arr2 })).toEqual(['xa', 'ya', 'za']);
471 | expect(parseExpression('MAP(arr2, REPT(FIRST(c), a.b))', { arr2 })).toEqual(['x', 'yy', 'zzz']);
472 | });
473 |
474 | test('FILTER op', () => {
475 | const arr = [
476 | {
477 | a: 1,
478 | b: 'x',
479 | },
480 | {
481 | a: 2,
482 | b: 'y',
483 | },
484 | {
485 | a: 3,
486 | b: 'z',
487 | },
488 | ];
489 | expect(parseExpression('FILTER(arr, EQUAL(a, 1))', { arr })).toEqual([arr[0]]);
490 | expect(parseExpression('FILTER(arr, EQUAL(b, "y"))', { arr })).toEqual([arr[1]]);
491 | expect(parseExpression('FILTER(arr, LT(a, 3))', { arr })).toEqual([arr[0], arr[1]]);
492 | });
493 |
494 | test('SORT op', () => {
495 | const arr = [
496 | {
497 | a: 2,
498 | b: 'y',
499 | },
500 | {
501 | a: 1,
502 | b: 'x',
503 | },
504 | {
505 | a: 3,
506 | b: 'z',
507 | },
508 | ];
509 | expect(parseExpression('SORT(arr, MULTIPLY(a, -1))', { arr })).toEqual([arr[2], arr[0], arr[1]]);
510 | expect(parseExpression('SORT(arr, b)', { arr })).toEqual([arr[1], arr[0], arr[2]]);
511 | });
512 | });
513 |
514 | describe('JSON ops', () => {
515 | test('JSON_PARSE op', () => {
516 | expect(parseExpression('JSON_PARSE(a)', { a: '{"a": 1}' })).toStrictEqual({ a: 1 });
517 | expect(parseExpression('JSON_PARSE("{\\"a\\": 1}")', {})).toStrictEqual({ a: 1 });
518 | expect(parseExpression('JSON_PARSE("{\\"a\\": {\\"b\\": \\"c\\"}}")', {})).toStrictEqual({ a: { b: 'c' } });
519 | expect(() => parseExpression('JSON_PARSE(a)', { a: '{"a": 1' })).toThrow(SyntaxError)
520 | });
521 |
522 | test('JSON_STRINGIFY op', () => {
523 | expect(parseExpression('JSON_STRINGIFY(a)', { a: { a: 1 } })).toBe('{"a":1}');
524 | });
525 |
526 | test('JSON_GET op', () => {
527 | expect(parseExpression('JSON_GET(a, "a")', { a: { a: 1 } })).toBe(1);
528 | expect(parseExpression('JSON_GET(a, "b")', { a: { a: 1 } })).toBe(null);
529 | expect(parseExpression('JSON_GET(AT(a, 0), "b")', { a: [{ b: 2 }] })).toBe(2);
530 | expect(parseExpression('JSON_GET(a, "a")', { a: 1 })).toBe(null);
531 | expect(parseExpression('JSON_GET(a, "a")', { a: null })).toBe(null);
532 | });
533 | });
534 |
535 | describe('Relational ops', () => {
536 | test('ASUM op', () => {
537 | expect(parseExpression('ASUM(a, b)', { a: [{b: 5}, {b: 10}, {b: 0}, {b: 15}] })).toBe(30);
538 | expect(parseExpression('ASUM(a, MULTIPLY(b, c))', { a: [{b: 5, c: 1}, {b: 10, c: 2}, {b: 1000, c: 0}, {b: 15, c: 10}] })).toBe(175);
539 | });
540 |
541 | test('AMIN op', () => {
542 | expect(parseExpression('AMIN(a, b)', { a: [{b: 5}, {b: 10}, {b: -5}, {b: 15}] })).toBe(-5);
543 | expect(parseExpression('AMIN(a, SUM(b, c))', { a: [{b: 5, c: -5}, {b: 10, c: 0}, {b: -5, c: 5}, {b: 15, c: -30}] })).toBe(-15);
544 | });
545 |
546 | test('AMAX op', () => {
547 | expect(parseExpression('AMAX(a, b)', { a: [{b: 5}, {b: 10}, {b: -5}, {b: 15}] })).toBe(15);
548 | expect(parseExpression('AMAX(a, SUM(b, c))', { a: [{b: 5, c: -5}, {b: 10, c: 0}, {b: -5, c: 5}, {b: 15, c: -30}] })).toBe(10);
549 | });
550 |
551 | test('AAVG op', () => {
552 | expect(parseExpression('AAVG(a, b)', { a: [{b: 5}, {b: 10}, {b: 0}, {b: 15}] })).toBe(7.5);
553 | expect(parseExpression('AAVG(a, SUM(b, c))', { a: [{b: 5, c: -5}, {b: 10, c: 0}, {b: -5, c: 5}, {b: 15, c: -30}] })).toBe(-1.25);
554 | });
555 |
556 | test('AMUL op', () => {
557 | expect(parseExpression('AMUL(a, b)', { a: [{b: 5}, {b: 10}, {b: 1}, {b: 15}] })).toBe(750);
558 | expect(parseExpression('AMUL(a, SUM(b, c))', { a: [{b: 10, c: 0}, {b: 15, c: -30}] })).toBe(-150);
559 | });
560 |
561 | test('AAND op', () => {
562 | expect(parseExpression('AAND(a, b)', { a: [{b: true}, {b: true}, {b: true}, {b: true}] })).toBe(true);
563 | expect(parseExpression('AAND(a, b)', { a: [{b: true}, {b: true}, {b: false}, {b: true}] })).toBe(false);
564 | expect(parseExpression('AAND(a, GT(b, 0))', { a: [{b: 1}, {b: 2}, {b: 3}, {b: 4}] })).toBe(true);
565 | expect(parseExpression('AAND(a, GT(b, 2))', { a: [{b: 1}, {b: 2}, {b: 3}, {b: 4}] })).toBe(false);
566 | });
567 |
568 | test('AOR op', () => {
569 | expect(parseExpression('AOR(a, b)', { a: [{b: false}, {b: false}, {b: false}, {b: false}] })).toBe(false);
570 | expect(parseExpression('AOR(a, b)', { a: [{b: false}, {b: false}, {b: true}, {b: false}] })).toBe(true);
571 | expect(parseExpression('AOR(a, EQUAL(b, 1))', { a: [{b: 1}, {b: 2}, {b: 3}, {b: 4}] })).toBe(true);
572 | expect(parseExpression('AOR(a, EQUAL(b, 0))', { a: [{b: 1}, {b: 2}, {b: 3}, {b: 4}] })).toBe(false);
573 | });
574 |
575 | test('ACOUNT op', () => {
576 | expect(parseExpression('ACOUNT(a, b)', { a: [{b: 1}, {b: 2}, {b: 3}, {b: 4}] })).toBe(4);
577 | expect(parseExpression('ACOUNT(a, GT(b, 1))', { a: [{b: 1}, {b: 2}, {b: 3}, {b: 4}] })).toBe(3);
578 | expect(parseExpression('ACOUNT(a, LTE(b, 2))', { a: [{b: 1}, {b: 2}, {b: 3}, {b: 4}] })).toBe(2);
579 | });
580 | });
581 |
582 | describe('Condition ops', () => {
583 | test('IF op', () => {
584 | expect(parseExpression('IF(a, b, c)', { a: true, b: 1, c: 2})).toBe(1);
585 | expect(parseExpression('IF(a, b, c)', { a: false, b: 1, c: 2})).toBe(2);
586 | expect(parseExpression('IF(a, b, c)', { a: 1, b: 1, c: 2})).toBe(2);
587 | expect(parseExpression('IF(a, b, c)', { a: '1', b: 1, c: 2})).toBe(2);
588 | expect(parseExpression('IF(a, b, c)', { a: {}, b: 1, c: 2})).toBe(2);
589 | expect(parseExpression('IF(a, b, c)', { a: [], b: 1, c: 2})).toBe(2);
590 | expect(parseExpression('IF(EQUAL(a, 5), b, c)', { a: 5, b: 1, c: 2})).toBe(1);
591 | expect(parseExpression('IF(AND(GT(a, 0), LT(a, 10)), b, c)', { a: 5, b: 1, c: 2})).toBe(1);
592 | });
593 |
594 | test('IFS op', () => {
595 | expect(parseExpression('IFS(a, b, c, d)', { a: true, b: 1, c: true, d: 2})).toBe(1);
596 | expect(parseExpression('IFS(a, b, c, d)', { a: true, b: 1, c: false, d: 2})).toBe(1);
597 | expect(parseExpression('IFS(a, b, c, d)', { a: false, b: 1, c: true, d: 2})).toBe(2);
598 | expect(parseExpression('IFS(a, b, c, d)', { a: false, b: 1, c: false, d: 2})).toBe(null);
599 | expect(parseExpression('IFS(a, b, c, d, e, f)', { a: true, b: 1, c: true, d: 2, e: true, f: 3})).toBe(1);
600 | expect(parseExpression('IFS(a, b, c, d, e, f)', { a: false, b: 1, c: true, d: 2, e: true, f: 3})).toBe(2);
601 | expect(parseExpression('IFS(a, b, c, d, e, f)', { a: false, b: 1, c: false, d: 2, e: true, f: 3})).toBe(3);
602 | });
603 | });
604 |
605 | describe('Other ops', () => {
606 | test('RANGE op', () => {
607 | expect(parseExpression('RANGE(a, b, c)', { a: 1, b: 5, c: 1 })).toEqual([1, 2, 3, 4, 5]);
608 | expect(parseExpression('RANGE(a, b, c)', { a: 5, b: 1, c: -1 })).toEqual([5, 4, 3, 2 ,1]);
609 | expect(parseExpression('RANGE(a, b, c)', { a: 1, b: 6, c: 2 })).toEqual([1, 3, 5]);
610 | expect(parseExpression('RANGE(a, b, c)', { a: 5, b: 0, c: -2 })).toEqual([5, 3, 1]);
611 | });
612 | });
613 |
614 | describe('Nested expressions', () => {
615 | test('Simple nested numeric expression', () => {
616 | expect(parseExpression('SUM(a, MULTIPLY(b, c))', { a: 1, b: 2, c: 3 })).toBe(7);
617 | });
618 |
619 | test('Complex nested numeric expression', () => {
620 | expect(parseExpression('SUM(a, MULTIPLY(b, SUM(c, d)))', { a: 1, b: 2, c: 3, d: 4 })).toBe(15);
621 | });
622 |
623 | test('Simple nested boolean expression', () => {
624 | expect(parseExpression('AND(a, OR(b, c))', { a: true, b: false, c: false })).toBe(false);
625 | });
626 |
627 | test('Complex nested boolean expression', () => {
628 | expect(parseExpression('AND(a, OR(b, AND(c, d)))', { a: true, b: false, c: true, d: false })).toBe(false);
629 | });
630 |
631 | test('Simple nested string expression', () => {
632 | expect(parseExpression('CONCAT(a, CONCAT(b, c))', { a: 'a', b: 'b', c: 'c' })).toBe('abc');
633 | });
634 |
635 | test('Complex nested string expression', () => {
636 | expect(parseExpression('CONCAT(a, CONCAT(b, CONCAT(c, d)))', { a: 'a', b: 'b', c: 'c', d: 'd' })).toBe('abcd');
637 | });
638 | });
639 |
640 | describe('Literal strings', () => {
641 | test('Simple string', () => {
642 | expect(parseExpression('"a"', {})).toBe('a');
643 | });
644 |
645 | test('String with escaped quotes', () => {
646 | expect(parseExpression('"a\\"b"', {})).toBe('a"b');
647 | });
648 |
649 | test('String with escaped backslash', () => {
650 | expect(parseExpression('"a\\b"', {})).toBe('a\\b');
651 | });
652 |
653 | test('String with parentheses and comma', () => {
654 | expect(parseExpression('"a(b,c)d"', {})).toBe('a(b,c)d');
655 | });
656 |
657 | test('String with all special characters', () => {
658 | expect(parseExpression('"a(b,c)d\\"e\\f"', {})).toBe('a(b,c)d"e\\f');
659 | });
660 |
661 | test('String operator 1', () => {
662 | expect(parseExpression('RIGHT(CONCAT(UPPER(CONCAT(a, "c")), 1), 3)', { a: 'ab' })).toBe('BC1');
663 | });
664 |
665 | test('String operator 2', () => {
666 | expect(parseExpression('EQUAL(CONCAT(LOWER("A,()\\""), a), "a,()\\"bc")', { a: 'bc' })).toBe(true);
667 | expect(parseExpression('EQUAL(CONCAT("A,()\\"", a), "a,()\\"bc")', { a: 'bc' })).toBe(false);
668 | expect(parseExpression('EQUAL(CONCAT("A,()\\"", a), "A,()\\"bc")', { a: 'bc' })).toBe(true);
669 | });
670 | });
671 | });
672 |
673 | describe('Test parseOp', () => {
674 | test('Simple unary op', () => {
675 | expect(parseOp('OP_(var)')).toStrictEqual({
676 | op: 'OP_',
677 | args: ['var'],
678 | });
679 | });
680 |
681 | test('Simple binary op', () => {
682 | expect(parseOp('OP_(var1,var2)')).toStrictEqual({
683 | op: 'OP_',
684 | args: ['var1', 'var2'],
685 | });
686 | });
687 |
688 | test('Literal number', () => {
689 | expect(parseOp('1')).toStrictEqual(null);
690 | });
691 |
692 | test('Field value', () => {
693 | expect(parseOp('a')).toStrictEqual(null);
694 | });
695 |
696 | test('Complex op 1', () => {
697 | expect(parseOp('OP_(OP_(var1))')).toStrictEqual({
698 | op: 'OP_',
699 | args: ['OP_(var1)'],
700 | });
701 | });
702 |
703 | test('Complex op 2', () => {
704 | expect(parseOp('OP_(OP_(var1),var2)')).toStrictEqual({
705 | op: 'OP_',
706 | args: ['OP_(var1)', 'var2'],
707 | });
708 | });
709 |
710 | test('Complex op 3', () => {
711 | expect(parseOp('OP_(OP_(var1),OP_(var2))')).toStrictEqual({
712 | op: 'OP_',
713 | args: ['OP_(var1)', 'OP_(var2)'],
714 | });
715 | });
716 |
717 | test('Complex op 4', () => {
718 | expect(parseOp('OP_(OP_(OP_(var1), var2),OP_(var3))')).toStrictEqual({
719 | op: 'OP_',
720 | args: ['OP_(OP_(var1), var2)', 'OP_(var3)'],
721 | });
722 | });
723 |
724 | test('Complex op 5', () => {
725 | expect(parseOp('OP_(OP_(OP_(var1), var2),OP_(var3, OP_(var4, var5)))')).toStrictEqual({
726 | op: 'OP_',
727 | args: ['OP_(OP_(var1), var2)', 'OP_(var3, OP_(var4, var5))'],
728 | });
729 | });
730 |
731 | test('Complex op 5', () => {
732 | expect(parseOp('OP_(OP_(OP_(var1, OP_(var2, OP_(var3, var4))), var5))')).toStrictEqual({
733 | op: 'OP_',
734 | args: ['OP_(OP_(var1, OP_(var2, OP_(var3, var4))), var5)'],
735 | });
736 | });
737 |
738 | test('Ternary op', () => {
739 | expect(parseOp('OP_(OP_(var1),var2,var3)')).toStrictEqual({
740 | op: 'OP_',
741 | args: ['OP_(var1)', 'var2', 'var3'],
742 | });
743 | });
744 |
745 | test('Contains space at both ends', () => {
746 | expect(parseOp(' OP_(var1) ')).toStrictEqual({
747 | op: 'OP_',
748 | args: ['var1'],
749 | });
750 | });
751 |
752 | test('Handle literal string', () => {
753 | expect(parseOp('OP_("(abc)\\", \\"(def)", ")(,\\"")')).toStrictEqual({
754 | op: 'OP_',
755 | args: ['"(abc)\\", \\"(def)"', '")(,\\""'],
756 | });
757 | });
758 |
759 | test('Handle literal string in complex op', () => {
760 | expect(parseOp('OP_(OP_(var1), OP_("(abc)\\", \\"(def)"))')).toStrictEqual({
761 | op: 'OP_',
762 | args: ['OP_(var1)', 'OP_("(abc)\\", \\"(def)")'],
763 | });
764 | expect(parseOp('OP_(OP_("(abc)\\", \\"(def)"), OP_(var1))')).toStrictEqual({
765 | op: 'OP_',
766 | args: ['OP_("(abc)\\", \\"(def)")', 'OP_(var1)'],
767 | });
768 | });
769 | });
770 |
771 | describe('Test toSlug', () => {
772 | test('English text', () => {
773 | expect(toSlug('We’ll always be with you. No one’s ever really gone. A thousand generations live in you now.'))
774 | .toBe('well-always-be-with-you-no-ones-ever-really-gone-a-thousand-generations-live-in-you-now');
775 |
776 | expect(toSlug('123 ABC !@# a12 []=-,./<>? DEF')).toBe('123-abc-a12-def');
777 | });
778 |
779 | test('Multi-line', () => {
780 | expect(toSlug(`
781 | We’ll always be with you.
782 | No one’s ever really gone.
783 | A thousand generations live in you now.`))
784 | .toBe('well-always-be-with-you-no-ones-ever-really-gone-a-thousand-generations-live-in-you-now');
785 | });
786 |
787 | test('Non-English text', () => {
788 | expect(toSlug(`
789 | Trăm năm trong cõi người ta,
790 | Chữ tài chữ mệnh khéo là ghét nhau.
791 | Trải qua một cuộc bể dâu,
792 | Những điều trông thấy mà đau đớn lòng.`))
793 | .toBe('tram-nam-trong-coi-nguoi-ta-chu-tai-chu-menh-kheo-la-ghet-nhau-trai-qua-mot-cuoc-be-dau-nhung-dieu-trong-thay-ma-dau-don-long')
794 | });
795 |
796 | test('Not a string', () => {
797 | expect(toSlug(1)).toBe('');
798 | expect(toSlug({})).toBe('');
799 | expect(toSlug([])).toBe('');
800 | expect(toSlug(new Date())).toBe('');
801 | expect(toSlug(new RegExp('123'))).toBe('');
802 | });
803 | });
804 |
--------------------------------------------------------------------------------
/src/operations.ts:
--------------------------------------------------------------------------------
1 | import { findValueByPath, isString } from './utils';
2 |
3 | export function parseExpression(
4 | exp: string,
5 | values: Record