From 9f87145f2755c1d1e88dd59f86fec750917bb408 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C5=A9=20=C4=90=E1=BB=A9c=20Duy?= Date: Sun, 30 Apr 2023 13:05:38 +0700 Subject: [PATCH 1/5] feat: add new numeric ops --- src/operations.test.ts | 30 ++++++++++++++++++++++++++++++ src/operations.ts | 27 +++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/src/operations.test.ts b/src/operations.test.ts index 79d3f665..93c5755b 100644 --- a/src/operations.test.ts +++ b/src/operations.test.ts @@ -115,6 +115,36 @@ describe('Test parseExpression', () => { expect(parseExpression('AVERAGE(a)', { a: 1 })).toBe(0); }); + test('CEIL op', () => { + expect(parseExpression('CEIL(a)', { a: 1.234 })).toBe(2); + }); + + test('FLOOR op', () => { + expect(parseExpression('FLOOR(a)', { a: 1.234 })).toBe(1); + }); + + test('ROUND op', () => { + expect(parseExpression('ROUND(a)', { a: 1.234 })).toBe(1); + }); + + test('EXP op', () => { + expect(parseExpression('EXP(a)', { a: 1 })).toBeCloseTo(Math.exp(1), 8); + }); + + test('LOG op', () => { + expect(parseExpression('LOG(a)', { a: 10 })).toBeCloseTo(Math.log(10), 8); + }); + + test('MAX op', () => { + expect(parseExpression('MAX(a)', { a: [1, 2, 30, 4, 5] })).toBe(30); + expect(parseExpression('MAX(a)', { a: 1 })).toBe(0); + }); + + test('MIN op', () => { + expect(parseExpression('MIN(a)', { a: [1, 2, 3, -4, 5] })).toBe(-4); + expect(parseExpression('MIN(a)', { a: 1 })).toBe(0); + }); + test('NULL op', () => { expect(parseExpression('NULL(a)', { a: null })).toBe(true); expect(parseExpression('NULL(a)', { a: undefined })).toBe(false); diff --git a/src/operations.ts b/src/operations.ts index d388eb79..11e51eb9 100644 --- a/src/operations.ts +++ b/src/operations.ts @@ -102,6 +102,33 @@ export function parseExpression(exp: string, values: Record, defaul } return 0; } + if (op === 'CEIL') { + return Math.ceil(valueA); + } + if (op === 'FLOOR') { + return Math.floor(valueA); + } + if (op === 'ROUND') { + return Math.round(valueA); + } + if (op === 'EXP') { + return Math.exp(valueA); + } + if (op === 'LOG') { + return Math.log(valueA); + } + if (op === 'MAX') { + if (valueA instanceof Array) { + return Math.max(...valueA); + } + return 0; + } + if (op === 'MIN') { + if (valueA instanceof Array) { + return Math.min(...valueA); + } + return 0; + } // boolean if (op === 'NULL') { return valueA === null; From 2d90dfcaa1d33468a9b433f6703b931c3a85cd8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C5=A9=20=C4=90=E1=BB=A9c=20Duy?= Date: Sun, 30 Apr 2023 13:21:14 +0700 Subject: [PATCH 2/5] refactor: refactor operations test --- src/operations.test.ts | 367 +++++++++++++++++++++-------------------- 1 file changed, 186 insertions(+), 181 deletions(-) diff --git a/src/operations.test.ts b/src/operations.test.ts index 93c5755b..cd1429a3 100644 --- a/src/operations.test.ts +++ b/src/operations.test.ts @@ -27,30 +27,28 @@ describe('Test parseExpression', () => { }); }); - test('INT op', () => { - expect(parseExpression('INT(a)', { a: '1' })).toBe(1); - }); - - test('FLOAT op', () => { - expect(parseExpression('FLOAT(a)', { a: '1.234' })).toBe(1.234); - }); + describe('Type conversion ops', () => { + test('INT op', () => { + expect(parseExpression('INT(a)', { a: '1' })).toBe(1); + }); - test('STRING op', () => { - expect(parseExpression('STRING(1)', {})).toBe('1'); - expect(parseExpression('STRING(a)', { a: 123 })).toBe('123'); - }); + test('FLOAT op', () => { + expect(parseExpression('FLOAT(a)', { a: '1.234' })).toBe(1.234); + }); - test('DATE op', () => { - expect(parseExpression('DATE(a)', { a: '2022-01-01' })).toEqual(new Date('2022-01-01')); - expect(parseExpression('DATE(a)', { a: 1640995200000 })).toEqual(new Date('2022-01-01')); - }); + test('STRING op', () => { + expect(parseExpression('STRING(1)', {})).toBe('1'); + expect(parseExpression('STRING(a)', { a: 123 })).toBe('123'); + }); - test('SLUG op', () => { - expect(parseExpression('SLUG(a)', { a: 'This is a title 123 !@#,./"' })).toBe('this-is-a-title-123-'); - }); + test('DATE op', () => { + expect(parseExpression('DATE(a)', { a: '2022-01-01' })).toEqual(new Date('2022-01-01')); + expect(parseExpression('DATE(a)', { a: 1640995200000 })).toEqual(new Date('2022-01-01')); + }); - test('CURRENCY op', () => { - expect(parseExpression('CURRENCY(a)', { a: 1000 })).toBe('1,000'); + test('CURRENCY op', () => { + expect(parseExpression('CURRENCY(a)', { a: 1000 })).toBe('1,000'); + }); }); describe('Date ops', () => { @@ -97,210 +95,217 @@ describe('Test parseExpression', () => { }); }); - test('ABS op', () => { - expect(parseExpression('ABS(a)', { a: -1 })).toBe(1); - }); - - test('SQRT op', () => { - expect(parseExpression('SQRT(a)', { a: 100 })).toBe(10); - }); - - test('SUM op', () => { - expect(parseExpression('SUM(a)', { a: [1, 2, 3, 4, 5] })).toBe(15); - expect(parseExpression('SUM(a)', { a: 1 })).toBe(0); - }); + describe('Arithmetic ops', () => { + test('ABS op', () => { + expect(parseExpression('ABS(a)', { a: -1 })).toBe(1); + }); - test('AVERAGE op', () => { - expect(parseExpression('AVERAGE(a)', { a: [1, 2, 3, 4, 5] })).toBe(3); - expect(parseExpression('AVERAGE(a)', { a: 1 })).toBe(0); - }); + test('SQRT op', () => { + expect(parseExpression('SQRT(a)', { a: 100 })).toBe(10); + }); - test('CEIL op', () => { - expect(parseExpression('CEIL(a)', { a: 1.234 })).toBe(2); - }); + test('SUM op', () => { + expect(parseExpression('SUM(a)', { a: [1, 2, 3, 4, 5] })).toBe(15); + expect(parseExpression('SUM(a)', { a: 1 })).toBe(0); + }); - test('FLOOR op', () => { - expect(parseExpression('FLOOR(a)', { a: 1.234 })).toBe(1); - }); + test('AVERAGE op', () => { + expect(parseExpression('AVERAGE(a)', { a: [1, 2, 3, 4, 5] })).toBe(3); + expect(parseExpression('AVERAGE(a)', { a: 1 })).toBe(0); + }); - test('ROUND op', () => { - expect(parseExpression('ROUND(a)', { a: 1.234 })).toBe(1); - }); + test('CEIL op', () => { + expect(parseExpression('CEIL(a)', { a: 1.234 })).toBe(2); + }); - test('EXP op', () => { - expect(parseExpression('EXP(a)', { a: 1 })).toBeCloseTo(Math.exp(1), 8); - }); + test('FLOOR op', () => { + expect(parseExpression('FLOOR(a)', { a: 1.234 })).toBe(1); + }); - test('LOG op', () => { - expect(parseExpression('LOG(a)', { a: 10 })).toBeCloseTo(Math.log(10), 8); - }); + test('ROUND op', () => { + expect(parseExpression('ROUND(a)', { a: 1.234 })).toBe(1); + expect(parseExpression('ROUND(a, b)', { a: 5, b: 2 })).toBe('5.00'); + }); - test('MAX op', () => { - expect(parseExpression('MAX(a)', { a: [1, 2, 30, 4, 5] })).toBe(30); - expect(parseExpression('MAX(a)', { a: 1 })).toBe(0); - }); + test('EXP op', () => { + expect(parseExpression('EXP(a)', { a: 1 })).toBeCloseTo(Math.exp(1), 8); + }); - test('MIN op', () => { - expect(parseExpression('MIN(a)', { a: [1, 2, 3, -4, 5] })).toBe(-4); - expect(parseExpression('MIN(a)', { a: 1 })).toBe(0); - }); + test('LOG op', () => { + expect(parseExpression('LOG(a)', { a: 10 })).toBeCloseTo(Math.log(10), 8); + }); - test('NULL op', () => { - expect(parseExpression('NULL(a)', { a: null })).toBe(true); - expect(parseExpression('NULL(a)', { a: undefined })).toBe(false); - expect(parseExpression('NULL(a)', { a: 0 })).toBe(false); - expect(parseExpression('NULL(a)', { a: '' })).toBe(false); - expect(parseExpression('NULL(a)', { a: {} })).toBe(false); - expect(parseExpression('NULL(a)', { a: [] })).toBe(false); - }); + test('MAX op', () => { + expect(parseExpression('MAX(a)', { a: [1, 2, 30, 4, 5] })).toBe(30); + expect(parseExpression('MAX(a)', { a: 1 })).toBe(0); + expect(parseExpression('MAX(a, b)', { a: 5, b: 2 })).toBe(5); + }); - test('NOT_NULL op', () => { - expect(parseExpression('NOT_NULL(a)', { a: null })).toBe(false); - expect(parseExpression('NOT_NULL(a)', { a: undefined })).toBe(true); - expect(parseExpression('NOT_NULL(a)', { a: 0 })).toBe(true); - expect(parseExpression('NOT_NULL(a)', { a: '' })).toBe(true); - expect(parseExpression('NOT_NULL(a)', { a: {} })).toBe(true); - expect(parseExpression('NOT_NULL(a)', { a: [] })).toBe(true); - }); + test('MIN op', () => { + expect(parseExpression('MIN(a)', { a: [1, 2, 3, -4, 5] })).toBe(-4); + expect(parseExpression('MIN(a)', { a: 1 })).toBe(0); + expect(parseExpression('MIN(a, b)', { a: 5, b: 2 })).toBe(2); + }); - test('NOT op', () => { - expect(parseExpression('NOT(a)', { a: false })).toBe(true); - expect(parseExpression('NOT(a)', { a: true })).toBe(false); - }); + test('SUM op', () => { + expect(parseExpression('SUM(a, b)', { a: 1, b: 2 })).toBe(3); + }); - test('STR_LEN op', () => { - expect(parseExpression('STR_LEN(a)', { a: '123' })).toBe(3); - expect(parseExpression('STR_LEN(a)', { a: 1 })).toBe(1); - }); + test('SUBTRACT op', () => { + expect(parseExpression('SUBTRACT(a, b)', { a: 5, b: 2 })).toBe(3); + expect(parseExpression('SUBTRACT(DATE(a), DATE(b))', { a: '2022-01-02', b: '2022-01-01' })).toBe(86400000); + }); - test('LOWER op', () => { - expect(parseExpression('LOWER(a)', { a: 'ABCDEF' })).toBe('abcdef'); - }); + test('MULTIPLY op', () => { + expect(parseExpression('MULTIPLY(a, b)', { a: 5, b: 2 })).toBe(10); + }); - test('UPPER op', () => { - expect(parseExpression('UPPER(a)', { a: 'abcdef' })).toBe('ABCDEF'); - }); + test('DIVIDE op', () => { + expect(parseExpression('DIVIDE(a, b)', { a: 5, b: 2 })).toBe(2.5); + }); - test('TRIM op', () => { - expect(parseExpression('TRIM(a)', { a: ' abc def ' })).toBe('abc def'); - }); + test('REMAINDER op', () => { + expect(parseExpression('REMAINDER(a, b)', { a: 5, b: 2 })).toBe(1); + }); - test('ARRAY_LEN op', () => { - expect(parseExpression('ARRAY_LEN(a)', { a: [1, 2, 3] })).toBe(3); - expect(parseExpression('ARRAY_LEN(a)', { a: 1 })).toBe(0); + test('POWER op', () => { + expect(parseExpression('POWER(a, b)', { a: 5, b: 2 })).toBe(25); + }); }); - test('SUM op', () => { - expect(parseExpression('SUM(a, b)', { a: 1, b: 2 })).toBe(3); - }); + describe('Boolean ops', () => { + test('NULL op', () => { + expect(parseExpression('NULL(a)', { a: null })).toBe(true); + expect(parseExpression('NULL(a)', { a: undefined })).toBe(false); + expect(parseExpression('NULL(a)', { a: 0 })).toBe(false); + expect(parseExpression('NULL(a)', { a: '' })).toBe(false); + expect(parseExpression('NULL(a)', { a: {} })).toBe(false); + expect(parseExpression('NULL(a)', { a: [] })).toBe(false); + }); - test('SUBTRACT op', () => { - expect(parseExpression('SUBTRACT(a, b)', { a: 5, b: 2 })).toBe(3); - expect(parseExpression('SUBTRACT(DATE(a), DATE(b))', { a: '2022-01-02', b: '2022-01-01' })).toBe(86400000); - }); + test('NOT_NULL op', () => { + expect(parseExpression('NOT_NULL(a)', { a: null })).toBe(false); + expect(parseExpression('NOT_NULL(a)', { a: undefined })).toBe(true); + expect(parseExpression('NOT_NULL(a)', { a: 0 })).toBe(true); + expect(parseExpression('NOT_NULL(a)', { a: '' })).toBe(true); + expect(parseExpression('NOT_NULL(a)', { a: {} })).toBe(true); + expect(parseExpression('NOT_NULL(a)', { a: [] })).toBe(true); + }); - test('MULTIPLY op', () => { - expect(parseExpression('MULTIPLY(a, b)', { a: 5, b: 2 })).toBe(10); - }); + test('NOT op', () => { + expect(parseExpression('NOT(a)', { a: false })).toBe(true); + expect(parseExpression('NOT(a)', { a: true })).toBe(false); + }); - test('DIVIDE op', () => { - expect(parseExpression('DIVIDE(a, b)', { a: 5, b: 2 })).toBe(2.5); - }); + test('EQUAL op', () => { + expect(parseExpression('EQUAL(a, b)', { a: 1, b: 1 })).toBe(true); + expect(parseExpression('EQUAL(a, b)', { a: 1, b: '1' })).toBe(false); + }); - test('REMAINDER op', () => { - expect(parseExpression('REMAINDER(a, b)', { a: 5, b: 2 })).toBe(1); - }); + test('NOT_EQUAL op', () => { + expect(parseExpression('NOT_EQUAL(a, b)', { a: 1, b: 1 })).toBe(false); + expect(parseExpression('NOT_EQUAL(a, b)', { a: 1, b: '1' })).toBe(true); + }); - test('ROUND op', () => { - expect(parseExpression('ROUND(a, b)', { a: 5, b: 2 })).toBe('5.00'); - }); + test('GT op', () => { + expect(parseExpression('GT(a, b)', { a: 1, b: 2 })).toBe(false); + expect(parseExpression('GT(a, b)', { a: 1, b: 1 })).toBe(false); + expect(parseExpression('GT(a, b)', { a: 2, b: 1 })).toBe(true); + }); - test('MAX op', () => { - expect(parseExpression('MAX(a, b)', { a: 5, b: 2 })).toBe(5); - }); + test('GTE op', () => { + expect(parseExpression('GTE(a, b)', { a: 1, b: 2 })).toBe(false); + expect(parseExpression('GTE(a, b)', { a: 1, b: 1 })).toBe(true); + expect(parseExpression('GTE(a, b)', { a: 2, b: 1 })).toBe(true); + }); - test('MIN op', () => { - expect(parseExpression('MIN(a, b)', { a: 5, b: 2 })).toBe(2); - }); + test('LT op', () => { + expect(parseExpression('LT(a, b)', { a: 1, b: 2 })).toBe(true); + expect(parseExpression('LT(a, b)', { a: 1, b: 1 })).toBe(false); + expect(parseExpression('LT(a, b)', { a: 2, b: 1 })).toBe(false); + }); - test('POWER op', () => { - expect(parseExpression('POWER(a, b)', { a: 5, b: 2 })).toBe(25); - }); + test('LTE op', () => { + expect(parseExpression('LTE(a, b)', { a: 1, b: 2 })).toBe(true); + expect(parseExpression('LTE(a, b)', { a: 1, b: 1 })).toBe(true); + expect(parseExpression('LTE(a, b)', { a: 2, b: 1 })).toBe(false); + }); - test('CONCAT op', () => { - expect(parseExpression('CONCAT(a, b)', { a: '123', b: '456' })).toBe('123456'); - }); + test('AND op', () => { + expect(parseExpression('AND(a, b)', { a: true, b: true })).toBe(true); + expect(parseExpression('AND(a, b)', { a: true, b: false })).toBe(false); + expect(parseExpression('AND(a, b)', { a: false, b: true })).toBe(false); + expect(parseExpression('AND(a, b)', { a: false, b: false })).toBe(false); + }); - test('LEFT op', () => { - expect(parseExpression('LEFT(a, b)', { a: '123456', b: 2 })).toBe('12'); + test('OR op', () => { + expect(parseExpression('OR(a, b)', { a: true, b: true })).toBe(true); + expect(parseExpression('OR(a, b)', { a: true, b: false })).toBe(true); + expect(parseExpression('OR(a, b)', { a: false, b: true })).toBe(true); + expect(parseExpression('OR(a, b)', { a: false, b: false })).toBe(false); + }); }); - test('RIGHT op', () => { - expect(parseExpression('RIGHT(a, b)', { a: '123456', b: 2 })).toBe('56'); - }); + describe('String ops', () => { + test('STR_LEN op', () => { + expect(parseExpression('STR_LEN(a)', { a: '123' })).toBe(3); + expect(parseExpression('STR_LEN(a)', { a: 1 })).toBe(1); + }); - test('EQUAL op', () => { - expect(parseExpression('EQUAL(a, b)', { a: 1, b: 1 })).toBe(true); - expect(parseExpression('EQUAL(a, b)', { a: 1, b: '1' })).toBe(false); - }); + test('LOWER op', () => { + expect(parseExpression('LOWER(a)', { a: 'ABCDEF' })).toBe('abcdef'); + }); - test('NOT_EQUAL op', () => { - expect(parseExpression('NOT_EQUAL(a, b)', { a: 1, b: 1 })).toBe(false); - expect(parseExpression('NOT_EQUAL(a, b)', { a: 1, b: '1' })).toBe(true); - }); + test('UPPER op', () => { + expect(parseExpression('UPPER(a)', { a: 'abcdef' })).toBe('ABCDEF'); + }); - test('GT op', () => { - expect(parseExpression('GT(a, b)', { a: 1, b: 2 })).toBe(false); - expect(parseExpression('GT(a, b)', { a: 1, b: 1 })).toBe(false); - expect(parseExpression('GT(a, b)', { a: 2, b: 1 })).toBe(true); - }); + test('TRIM op', () => { + expect(parseExpression('TRIM(a)', { a: ' abc def ' })).toBe('abc def'); + }); - test('GTE op', () => { - expect(parseExpression('GTE(a, b)', { a: 1, b: 2 })).toBe(false); - expect(parseExpression('GTE(a, b)', { a: 1, b: 1 })).toBe(true); - expect(parseExpression('GTE(a, b)', { a: 2, b: 1 })).toBe(true); - }); + test('CONCAT op', () => { + expect(parseExpression('CONCAT(a, b)', { a: '123', b: '456' })).toBe('123456'); + }); - test('LT op', () => { - expect(parseExpression('LT(a, b)', { a: 1, b: 2 })).toBe(true); - expect(parseExpression('LT(a, b)', { a: 1, b: 1 })).toBe(false); - expect(parseExpression('LT(a, b)', { a: 2, b: 1 })).toBe(false); - }); + test('LEFT op', () => { + expect(parseExpression('LEFT(a, b)', { a: '123456', b: 2 })).toBe('12'); + }); - test('LTE op', () => { - expect(parseExpression('LTE(a, b)', { a: 1, b: 2 })).toBe(true); - expect(parseExpression('LTE(a, b)', { a: 1, b: 1 })).toBe(true); - expect(parseExpression('LTE(a, b)', { a: 2, b: 1 })).toBe(false); - }); + test('RIGHT op', () => { + expect(parseExpression('RIGHT(a, b)', { a: '123456', b: 2 })).toBe('56'); + }); - test('AND op', () => { - expect(parseExpression('AND(a, b)', { a: true, b: true })).toBe(true); - expect(parseExpression('AND(a, b)', { a: true, b: false })).toBe(false); - expect(parseExpression('AND(a, b)', { a: false, b: true })).toBe(false); - expect(parseExpression('AND(a, b)', { a: false, b: false })).toBe(false); + test('SLUG op', () => { + expect(parseExpression('SLUG(a)', { a: 'This is a title 123 !@#,./"' })).toBe('this-is-a-title-123-'); + }); }); - test('OR op', () => { - expect(parseExpression('OR(a, b)', { a: true, b: true })).toBe(true); - expect(parseExpression('OR(a, b)', { a: true, b: false })).toBe(true); - expect(parseExpression('OR(a, b)', { a: false, b: true })).toBe(true); - expect(parseExpression('OR(a, b)', { a: false, b: false })).toBe(false); + describe('Array ops', () => { + test('ARRAY_LEN op', () => { + expect(parseExpression('ARRAY_LEN(a)', { a: [1, 2, 3] })).toBe(3); + expect(parseExpression('ARRAY_LEN(a)', { a: 1 })).toBe(0); + }); }); - test('ASUM op', () => { - expect(parseExpression('ASUM(a, b)', { a: [{b: 5}, {b: 10}, {b: 0}, {b: 15}] })).toBe(30); - 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); + describe('Relational ops', () => { + test('ASUM op', () => { + expect(parseExpression('ASUM(a, b)', { a: [{b: 5}, {b: 10}, {b: 0}, {b: 15}] })).toBe(30); + 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); + }); }); - test('IF op', () => { - expect(parseExpression('IF(a, b, c)', { a: true, b: 1, c: 2})).toBe(1); - expect(parseExpression('IF(a, b, c)', { a: false, b: 1, c: 2})).toBe(2); - expect(parseExpression('IF(a, b, c)', { a: 1, b: 1, c: 2})).toBe(2); - expect(parseExpression('IF(a, b, c)', { a: '1', b: 1, c: 2})).toBe(2); - expect(parseExpression('IF(a, b, c)', { a: {}, b: 1, c: 2})).toBe(2); - expect(parseExpression('IF(a, b, c)', { a: [], b: 1, c: 2})).toBe(2); - expect(parseExpression('IF(EQUAL(a, 5), b, c)', { a: 5, b: 1, c: 2})).toBe(1); - expect(parseExpression('IF(AND(GT(a, 0), LT(a, 10)), b, c)', { a: 5, b: 1, c: 2})).toBe(1); + describe('Condition ops', () => { + test('IF op', () => { + expect(parseExpression('IF(a, b, c)', { a: true, b: 1, c: 2})).toBe(1); + expect(parseExpression('IF(a, b, c)', { a: false, b: 1, c: 2})).toBe(2); + expect(parseExpression('IF(a, b, c)', { a: 1, b: 1, c: 2})).toBe(2); + expect(parseExpression('IF(a, b, c)', { a: '1', b: 1, c: 2})).toBe(2); + expect(parseExpression('IF(a, b, c)', { a: {}, b: 1, c: 2})).toBe(2); + expect(parseExpression('IF(a, b, c)', { a: [], b: 1, c: 2})).toBe(2); + expect(parseExpression('IF(EQUAL(a, 5), b, c)', { a: 5, b: 1, c: 2})).toBe(1); + expect(parseExpression('IF(AND(GT(a, 0), LT(a, 10)), b, c)', { a: 5, b: 1, c: 2})).toBe(1); + }); }); describe('Nested expressions', () => { From 2e5193884eb1eb8c276424a79e7572b5067d996b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C5=A9=20=C4=90=E1=BB=A9c=20Duy?= Date: Sun, 30 Apr 2023 16:36:18 +0700 Subject: [PATCH 3/5] feature: add new string ops --- src/operations.test.ts | 31 +++++++++++++++++++++++++++++++ src/operations.ts | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/src/operations.test.ts b/src/operations.test.ts index cd1429a3..b2e72c97 100644 --- a/src/operations.test.ts +++ b/src/operations.test.ts @@ -264,6 +264,10 @@ describe('Test parseExpression', () => { expect(parseExpression('TRIM(a)', { a: ' abc def ' })).toBe('abc def'); }); + test('ENCODE_URL_COMPONENT op', () => { + expect(parseExpression('ENCODE_URL_COMPONENT(a)', { a: 'abc def' })).toBe('abc%20def'); + }); + test('CONCAT op', () => { expect(parseExpression('CONCAT(a, b)', { a: '123', b: '456' })).toBe('123456'); }); @@ -276,9 +280,36 @@ describe('Test parseExpression', () => { expect(parseExpression('RIGHT(a, b)', { a: '123456', b: 2 })).toBe('56'); }); + test('MID op', () => { + expect(parseExpression('MID(a, b, c)', { a: '123456', b: 1, c: 2 })).toBe('23'); + }); + test('SLUG op', () => { expect(parseExpression('SLUG(a)', { a: 'This is a title 123 !@#,./"' })).toBe('this-is-a-title-123-'); }); + + test('REPT op', () => { + expect(parseExpression('REPT(a, b)', { a: '123', b: 3 })).toBe('123123123'); + }); + + test('JOIN op', () => { + expect(parseExpression('JOIN(a, " - ")', { a: ['a', 'b', 'c'] })).toBe('a - b - c'); + }); + + test('SPLIT op', () => { + expect(parseExpression('SPLIT(a, " - ")', { a: 'a - b - c' })).toEqual(['a', 'b', 'c']); + }); + + test('SUBSTITUTE op', () => { + expect(parseExpression('SUBSTITUTE(a, "a", "b")', { a: 'abcabc' })).toBe('bbcbbc'); + expect(parseExpression('SUBSTITUTE(a, "d", "b")', { a: 'abcabc' })).toBe('abcabc'); + }); + + test('SEARCH op', () => { + expect(parseExpression('SEARCH(a, "b")', { a: 'abcabc' })).toBe(1); + expect(parseExpression('SEARCH(a, "b", 3)', { a: 'abcabc' })).toBe(4); + expect(parseExpression('SEARCH(a, "d")', { a: 'abcabc' })).toBe(-1); + }); }); describe('Array ops', () => { diff --git a/src/operations.ts b/src/operations.ts index 11e51eb9..bbd985c2 100644 --- a/src/operations.ts +++ b/src/operations.ts @@ -152,6 +152,9 @@ export function parseExpression(exp: string, values: Record, defaul if (op === 'TRIM') { return String(valueA).trim(); } + if (op === 'ENCODE_URL_COMPONENT') { + return encodeURIComponent(valueA); + } // array if (op === 'ARRAY_LEN') { if (valueA instanceof Array) { @@ -205,6 +208,23 @@ export function parseExpression(exp: string, values: Record, defaul if (op === 'RIGHT') { return String(valueA).slice(-Number(valueB)); } + if (op === 'REPT') { + return String(valueA).repeat(Number(valueB)); + } + if (op === 'JOIN') { + if (valueA instanceof Array) { + return valueA.join(String(valueB)); + } + return ''; + } + if (op === 'SPLIT') { + return String(valueA).split(String(valueB)); + } + if (op === 'SEARCH') { + const str = String(parseExpression(args[0], values, defaultValues)); + const find = String(parseExpression(args[1], values, defaultValues)); + return str.indexOf(find); + } // boolean if (op === 'EQUAL') { return valueA === valueB; @@ -237,6 +257,24 @@ export function parseExpression(exp: string, values: Record, defaul } return parseExpression(args[2], values, defaultValues); } + if (op === 'MID') { + const str = String(parseExpression(args[0], values, defaultValues)); + const startAt = Number(parseExpression(args[1], values, defaultValues)); + const count = Number(parseExpression(args[2], values, defaultValues)); + return str.slice(startAt, startAt + count); + } + if (op === 'SUBSTITUTE') { + const str = String(parseExpression(args[0], values, defaultValues)); + const old = String(parseExpression(args[1], values, defaultValues)); + const newStr = String(parseExpression(args[2], values, defaultValues)); + return str.split(old).join(newStr); + } + if (op === 'SEARCH') { + const str = String(parseExpression(args[0], values, defaultValues)); + const find = String(parseExpression(args[1], values, defaultValues)); + const startAt = Number(parseExpression(args[2], values, defaultValues)); + return str.indexOf(find, startAt); + } } } From 9692c140870c8430a67a9773667025842774dec4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C5=A9=20=C4=90=E1=BB=A9c=20Duy?= Date: Sun, 30 Apr 2023 22:06:00 +0700 Subject: [PATCH 4/5] feat: add new date ops --- package.json | 2 +- src/operations.test.ts | 10 ++++++++++ src/operations.ts | 16 ++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 37938ad0..9838dad5 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "scripts": { "build": "directus-extension build", "dev": "directus-extension build -w --no-minify", - "test": "jest" + "test": "TZ=UTC jest" }, "devDependencies": { "@babel/core": "^7.19.3", diff --git a/src/operations.test.ts b/src/operations.test.ts index b2e72c97..ce752a37 100644 --- a/src/operations.test.ts +++ b/src/operations.test.ts @@ -62,6 +62,16 @@ describe('Test parseExpression', () => { expect(parseExpression('DATE_UTC($NOW)', {})).toBe('Sun, 01 Jan 2023 00:00:00 GMT'); }); + test('DATE_STR op', () => { + expect(parseExpression('DATE_STR(a)', { a: new Date('2022-12-31') })).toBe('2022-12-31'); + expect(parseExpression('DATE_STR($NOW)', {})).toBe('2023-01-01'); + }); + + test('TIME_STR op', () => { + expect(parseExpression('TIME_STR(a)', { a: new Date('2022-12-31T11:59:59') })).toBe('11:59:59'); + expect(parseExpression('TIME_STR($NOW)', {})).toBe('00:00:00'); + }); + test('YEAR op', () => { expect(parseExpression('YEAR($NOW)', {})).toBe(new Date().getFullYear()); }); diff --git a/src/operations.ts b/src/operations.ts index bbd985c2..643bd0bf 100644 --- a/src/operations.ts +++ b/src/operations.ts @@ -67,6 +67,22 @@ export function parseExpression(exp: string, values: Record, defaul if (op === 'DATE_UTC') { return new Date(valueA).toUTCString(); } + if (op === 'DATE_STR') { + // format YYYY-MM-DD + const date = new Date(valueA); + const year = date.getFullYear(); + const month = (date.getMonth() + 1).toString().padStart(2, '0'); + const day = date.getDate().toString().padStart(2, '0'); + return `${year}-${month}-${day}`; + } + if (op === 'TIME_STR') { + // format HH:MM:SS + const date = new Date(valueA); + const hours = date.getHours().toString().padStart(2, '0'); + const minutes = date.getMinutes().toString().padStart(2, '0'); + const seconds = date.getSeconds().toString().padStart(2, '0'); + return `${hours}:${minutes}:${seconds}`; + } if (['YEAR', 'MONTH', 'GET_DATE', 'DAY', 'HOURS', 'MINUTES', 'SECONDS', 'TIME'].includes(op)) { if (valueA instanceof Date) { const op2func = { From a062a1a6bb53f23efdf153366e4434944b9f22dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C5=A9=20=C4=90=E1=BB=A9c=20Duy?= Date: Sun, 30 Apr 2023 22:35:41 +0700 Subject: [PATCH 5/5] docs: update readme with new ops --- README.md | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 4da99d66..c165240b 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,8 @@ Operator | Description --- | --- `DATE_ISO(a)` | transform date or date-like object to ISO string `DATE_UTC(a)` | transform date or date-like object to UTC string +`DATE_STR(a)` | transform date or date-like object to string with format "YYYY-MM-DD" +`TIME_STR(a)` | transform date or date-like object to string with format "HH:mm:ss" `YEAR(a)` | get year of a date object, similar to `getFullYear` `MONTH(a)` | get month of a date object, similar to `getMonth` `GET_DATE(a)` | get date of a date object, similar to `getDate` @@ -111,22 +113,37 @@ Operator | Description `MULTIPLY(a, b)` | a * b `DIVIDE(a, b)` | a / b `REMAINDER(a, b)` | a % b -`ROUND(a, n)` | round number `a` to `n` number of decimals, similar to `toFixed` -`MAX(a, b)` | max value -`MIN(a, b)` | min value +`CEIL(a)` | returns the smallest integer greater than or equal to `a`. +`FLOOR(a)` | returns the largest integer less than or equal to `a`. +`ROUND(a)` | rounds to the nearest integer. +`ROUND(a, n)` | rounds number `a` to `n` number of decimals, (`ROUND(1.23, 1) = 1.2`). +`MAX(a, b)` | max value between `a` and `b`. +`MAX(arr)` | max value of an array of numbers. +`MIN(a, b)` | min value between `a` and `b`. +`MIN(arr)` | min value of an array of numbers. `POWER(a, b)` | a^b +`EXP(a)` | returns `e^a`, where `e` is Euler's number. +`LOG(a)` | returns the natural logarithm (base `e`) of `a`. ### String Operator | Description --- | --- -`STR_LEN(a)` | length of string -`LOWER(a)` | to lower case -`UPPER(a)` | to upper case -`TRIM(a)` | removes whitespace at the beginning and end of string. -`CONCAT(a, b)` | concat 2 strings `a` and `b`. -`LEFT(a, b)` | extract `b` characters from the beginning of the string `a`. -`RIGHT(a, b)` | extract `b` characters from the end of the string `a`. +`STR_LEN(str)` | length of string +`LOWER(str)` | to lower case +`UPPER(str)` | to upper case +`TRIM(str)` | removes whitespace at the beginning and end of string. +`CONCAT(strA, strB)` | concat 2 strings `strA` and `strB`. +`LEFT(str, count)` | extract `count` characters from the beginning of the string `str`. +`RIGHT(str, count)` | extract `count` characters from the end of the string `str`. +`MID(str, startAt, count)` | extract `count` characters from `startAt` position of the string `str`. +`ENCODE_URL_COMPONENT(str)` | encode string to URL component. +`REPT(str, count)` | repeat string `count` times. +`JOIN(arr, separator)` | join an array of strings with `separator`. +`SPLIT(str, separator)` | split string `str` by `separator` to an array of strings. +`SEARCH(str, keyword)` | search `keyword` in `str` and return the position of the first occurrence. Return -1 if not found. +`SEARCH(str, keyword, startAt)` | search `keyword` in `str` and return the position of the first occurrence after `startAt`. Return -1 if not found. +`SUBSTITUTE(str, old, new)` | replace all occurrences of `old` in `str` with `new`. ### Boolean