diff --git a/libraries/adaptive-expressions/src/expressionFunctions.ts b/libraries/adaptive-expressions/src/expressionFunctions.ts index 998d8ef2e1..298f9f5659 100644 --- a/libraries/adaptive-expressions/src/expressionFunctions.ts +++ b/libraries/adaptive-expressions/src/expressionFunctions.ts @@ -2818,11 +2818,6 @@ export class ExpressionFunctions { ReturnType.Number, ExpressionFunctions.validateBinaryNumber), new ExpressionEvaluator(ExpressionType.CreateArray, ExpressionFunctions.apply((args: any []): any[] => Array.from(args)), ReturnType.Object), - new ExpressionEvaluator( - ExpressionType.Array, - ExpressionFunctions.apply((args: any []): any[] => [args[0]], ExpressionFunctions.verifyString), - ReturnType.Object, - ExpressionFunctions.validateUnary), new ExpressionEvaluator( ExpressionType.Binary, ExpressionFunctions.apply((args: any []): string => this.toBinary(args[0]), ExpressionFunctions.verifyString), @@ -2988,7 +2983,37 @@ export class ExpressionFunctions { return {value, error}; }), ReturnType.Boolean, - ExpressionFunctions.validateIsMatch) + ExpressionFunctions.validateIsMatch), + + // Type Checking Functions + new ExpressionEvaluator(ExpressionType.isString, ExpressionFunctions.apply( + (args: any[]): boolean => typeof args[0] === 'string'), + ReturnType.Boolean, + ExpressionFunctions.validateUnary), + new ExpressionEvaluator(ExpressionType.isInteger, ExpressionFunctions.apply( + (args: any[]): boolean => this.isNumber(args[0]) && Number.isInteger(args[0])), + ReturnType.Boolean, + ExpressionFunctions.validateUnary), + new ExpressionEvaluator(ExpressionType.isFloat, ExpressionFunctions.apply( + (args: any[]): boolean => this.isNumber(args[0]) && !Number.isInteger(args[0])), + ReturnType.Boolean, + ExpressionFunctions.validateUnary), + new ExpressionEvaluator(ExpressionType.isArray, ExpressionFunctions.apply( + (args: any[]): boolean => Array.isArray(args[0])), + ReturnType.Boolean, + ExpressionFunctions.validateUnary), + new ExpressionEvaluator(ExpressionType.isObject, ExpressionFunctions.apply( + (args: any[]): boolean => typeof args[0] === 'object'), + ReturnType.Boolean, + ExpressionFunctions.validateUnary), + new ExpressionEvaluator(ExpressionType.isBoolean, ExpressionFunctions.apply( + (args: any[]): boolean => typeof args[0] === 'boolean'), + ReturnType.Boolean, + ExpressionFunctions.validateUnary), + new ExpressionEvaluator(ExpressionType.isDateTime, ExpressionFunctions.apply( + (args: any[]): boolean => typeof args[0] === 'string' && this.verifyISOTimestamp(args[0]) === undefined), + ReturnType.Boolean, + ExpressionFunctions.validateUnary) ]; const lookup: Map = new Map(); diff --git a/libraries/adaptive-expressions/src/expressionType.ts b/libraries/adaptive-expressions/src/expressionType.ts index 233f1b2bc5..b2433cdcc2 100644 --- a/libraries/adaptive-expressions/src/expressionType.ts +++ b/libraries/adaptive-expressions/src/expressionType.ts @@ -90,7 +90,6 @@ export class ExpressionType { public static readonly Int: string = 'int'; public static readonly String: string = 'string'; public static readonly Bool: string = 'bool'; - public static readonly Array: string = 'array'; public static readonly Binary: string = 'binary'; public static readonly DataUri: string = 'dataUri'; public static readonly DataUriToBinary: string = 'dataUriToBinary'; @@ -154,4 +153,13 @@ export class ExpressionType { // Regar expression public static readonly IsMatch: string = 'isMatch'; + + //Type Checking + public static readonly isString: string = 'isString'; + public static readonly isInteger: string = 'isInteger'; + public static readonly isArray: string = 'isArray'; + public static readonly isObject: string = 'isObject'; + public static readonly isFloat: string = 'isFloat'; + public static readonly isDateTime: string = 'isDateTime'; + public static readonly isBoolean: string = 'isBoolean'; } diff --git a/libraries/adaptive-expressions/tests/badExpression.test.js b/libraries/adaptive-expressions/tests/badExpression.test.js index 073ae20da5..40480c8961 100644 --- a/libraries/adaptive-expressions/tests/badExpression.test.js +++ b/libraries/adaptive-expressions/tests/badExpression.test.js @@ -343,7 +343,16 @@ const badExpressions = // SetPathToValue tests 'setPathToValue(2+3, 4)', // Not a real path - 'setPathToValue(a)' // Missing value + 'setPathToValue(a)', // Missing value + + //Type Checking + 'isString(hello, hello)', // should have one parameter + 'isInteger(one, hello)', // should have one parameter + 'isFloat(1.324, hello)', // should have one parameter + 'isArray(createArrat(1,2,3), hello)', // should have one parameter + 'isBoolean(true, false)', // should have one parameter + 'isDateTime("2018-03-15T13:00:00.111Z", hello)', // should have one parameter + 'isObject({}, false)', // should have one parameter ]; const scope = { diff --git a/libraries/adaptive-expressions/tests/expressionParser.test.js b/libraries/adaptive-expressions/tests/expressionParser.test.js index f38fe2e8db..8a4dceb896 100644 --- a/libraries/adaptive-expressions/tests/expressionParser.test.js +++ b/libraries/adaptive-expressions/tests/expressionParser.test.js @@ -24,16 +24,26 @@ const dataSource = [ // Operators tests ['1 + 2', 3], + ['1 +\n 2', 3], + ['1 \n+ 2', 3], + ['1 +\r\n 2', 3], ['- 1 + 2', 1], + ['-\r\n 1 + 2', 1], ['+ 1 + 2', 3], + ['+\r\n 1 + 2', 3], ['1 - 2', -1], + ['1 -\r\n 2', -1], ['1 - (-2)', 3], + ['1 - (\r\n-2)', 3], ['1.0 + 2.0', 3.0], ['1 * 2 + 3', 5], + ['1 *\r\n 2 + 3', 5], ['1 + 2 * 3', 7], ['4 / 2', 2], + ['4 /\r\n 2', 2], ['1 + 3 / 2', 2], ['(1 + 3) / 2', 2], + ['(1 +\r\n 3) / 2', 2], ['1 * (2 + 3)', 5], ['(1 + 2) * 3', 9], ['(one + two) * bag.three', 9.0, ['one', 'two', 'bag.three']], @@ -43,14 +53,20 @@ const dataSource = [ ['one + two + hello + one + two', '3hello12'], ['2^2', 4.0], + ['2^\r\n2', 4.0], + ['3^2^2', 81.0], + ['one >\r\n 0.5', true], ['one > 0.5 && two < 2.5', true], ['one > 0.5 || two < 1.5', true], ['5 % 2', 1], + ['5 %\r\n 2', 1], ['!(one == 1.0)', false], + ['!\r\n(one == 1.0)', false], ['!!(one == 1.0)', true], ['!exists(xione) || !!exists(two)', true], ['(1 + 2) == (4 - 1)', true], + ['(1 + 2) ==\r\n (4 - 1)', true], ['!!exists(one) == !!exists(one)', true], ['!(one == 1.0)', false, ['one']], ['!!(one == 1.0)', true, ['one']], @@ -61,6 +77,7 @@ const dataSource = [ ['hello == \'hello\'', true], ['hello == \'world\'', false], ['(1 + 2) != (4 - 1)', false], + ['(1 + 2) !=\r\n (4 - 1)', false], ['!!exists(one) != !!exists(one)', false], ['hello != \'hello\'', false], ['hello != \'world\'', true], @@ -68,18 +85,27 @@ const dataSource = [ ['hello != "world"', true], ['(1 + 2) >= (4 - 1)', true], ['(2 + 2) >= (4 - 1)', true], + ['(2 + 2) >=\r\n (4 - 1)', true], ['float(5.5) >= float(4 - 1)', true], ['(1 + 2) <= (4 - 1)', true], ['(2 + 2) <= (4 - 1)', false], + ['(2 + 2) <=\r\n (4 - 1)', false], ['float(5.5) <= float(4 - 1)', false], ['\'string\'&\'builder\'', 'stringbuilder'], ['"string"&"builder"', 'stringbuilder'], + ['"string"&\n"builder"', 'stringbuilder'], + ['"string"&\r\n"builder"', 'stringbuilder'], ['one > 0.5 && two < 2.5', true, oneTwo], ['notThere > 4', false], ['float(5.5) && float(0.0)', true], ['hello && "hello"', true], + ['hello &&\n "hello"', true], + ['hello &&\r\n "hello"', true], ['items || ((2 + 2) <= (4 - 1))', true], // true || false ['0 || false', true], // true || false + ['0 ||\n false', true], // true || false + ['0 ||\r\n false', true], // true || false + ['false ||\r\n false || \r\n true', true], // true || false ['!(hello)', false], // false ['!(10)', false], ['!(0)', false], @@ -91,9 +117,11 @@ const dataSource = [ ['concat(hello,world)', 'helloworld'], ['concat(hello,nullObj)', 'hello'], ['concat(\'hello\',\'world\')', 'helloworld'], + ['concat(\'hello\'\r\n,\'world\')', 'helloworld'], ['concat("hello","world")', 'helloworld'], ['add(hello,world)', 'helloworld'], ['add(\'hello\',\'world\')', 'helloworld'], + ['add(\'hello\',\r\n\'world\')', 'helloworld'], ['add(nullObj,\'world\')', 'world'], ['add(\'hello\',nullObj)', 'hello'], ['add("hello","world")', 'helloworld'], @@ -104,7 +132,7 @@ const dataSource = [ ['length(hello + world)', 10], ['count(\'hello\')', 5], ['count("hello")', 5], - ['count(concat(hello,world))', 10], + ['count(concat(hello,\r\nworld))', 10], ['replace(\'hello\', \'l\', \'k\')', 'hekko'], ['replace(\'hello\', \'L\', \'k\')', 'hello'], ['replace(nullObj, \'L\', \'k\')', ''], @@ -276,7 +304,6 @@ const dataSource = [ ['bool(\'hi\')', true], ['createArray(\'h\', \'e\', \'l\', \'l\', \'o\')', ['h', 'e', 'l', 'l', 'o']], ['createArray(1, bool(0), string(bool(1)), float(\'10\'))', [1, true, 'true', 10.0]], - ['array(hello)', ['hello']], ['binary(hello)', '0110100001100101011011000110110001101111'], ['dataUri(hello)', 'data:text/plain;charset=utf-8;base64,aGVsbG8='], ['dataUriToBinary(dataUri(hello))', '011001000110000101110100011000010011101001110100011001010111100001110100001011110111000001101100011000010110100101101110001110110110001101101000011000010111001001110011011001010111010000111101011101010111010001100110001011010011100000111011011000100110000101110011011001010011011000110100001011000110000101000111010101100111001101100010010001110011100000111101'], @@ -342,8 +369,8 @@ const dataSource = [ ['formatDateTime(notISOTimestamp, \'ddd\')', 'Thu'], ['formatDateTime(notISOTimestamp, \'dddd\')', 'Thursday'], ['formatDateTime(\'2018-03-15T00:00:00.000Z\', \'yyyy\')', '2018'], -// ['formatDateTime(\'2018-03-15T00:00:00.000Z\', \'yyyy-MM-dd-\\\\d\')', '2018-03-15-4'], -// - Fails in the US + // ['formatDateTime(\'2018-03-15T00:00:00.000Z\', \'yyyy-MM-dd-\\\\d\')', '2018-03-15-4'], + // - Fails in the US ['formatDateTime(\'2018-03-15T00:00:00.010Z\', \'FFFF\')', '0100'], ['formatDateTime(\'2018-03-15T00:00:00.010Z\', \'FFFFFF\')', '010000'], ['formatDateTime(\'2018-03-15T00:00:00.010Z\', \'FFF\')', '010'], @@ -366,9 +393,9 @@ const dataSource = [ ['subtractFromTime(timestamp, 1, \'Hour\')', '2018-03-15T12:00:00.111Z'], ['subtractFromTime(timestamp, 1, \'Minute\')', '2018-03-15T12:59:00.111Z'], ['subtractFromTime(timestamp, 1, \'Second\')', '2018-03-15T12:59:59.111Z'], -// ['dateReadBack(timestamp, addDays(timestamp, 1))', 'tomorrow'], -// ['dateReadBack(addDays(timestamp, 1),timestamp)', 'yesterday'], -// - Fails in the US + // ['dateReadBack(timestamp, addDays(timestamp, 1))', 'tomorrow'], + // ['dateReadBack(addDays(timestamp, 1),timestamp)', 'yesterday'], + // - Fails in the US ['getTimeOfDay(\'2018-03-15T00:00:00.000Z\')', 'midnight'], ['getTimeOfDay(\'2018-03-15T08:00:00.000Z\')', 'morning'], ['getTimeOfDay(\'2018-03-15T12:00:00.000Z\')', 'noon'], @@ -405,6 +432,7 @@ const dataSource = [ ['uriScheme(\'http://www.contoso.com/catalog/shownew.htm?date=today\')', 'http'], // Collection functions tests + ['createArray(hello)', ['hello']], ['sum(createArray(1, 2))', 3], ['sum(createArray(one, two, 3))', 6.0], ['average(createArray(1, 2))', 1.5], @@ -514,6 +542,24 @@ const dataSource = [ ['isMatch("a", "\\\\w{1}")', true], // "\w" (match [a-zA-Z0-9_]) ['isMatch("1", "\\\\d{1}")', true], // "\d" (match [0-9]) + //Type Checking Tests + ['isString(hello)', true], + ['isString("Monday")', true], + ['isString(one)', false], + ['isInteger(one)', true], + ['isInteger(1)', true], + ['isInteger(1.23)', false], + ['isFloat(one)', false], + ['isFloat(1)', false], + ['isFloat(1.23)', true], + ['isArray(hello)', false], + ['isArray(createArray(1,2,3))', true], + ['isObject(hello)', false], + ['isObject(dialog)', true], + ['isBoolean(hello)', false], + ['isBoolean(1 == one)', true], + ['isDateTime(hello)', false], + ['isDateTime(timestamp)', true], // Empty expression ['', ''],