mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 01:56:46 +00:00
feat(editor): Add missing extension methods for expressions (#8845)
This commit is contained in:
@@ -320,6 +320,26 @@ function intersection(value: unknown[], extraArgs: unknown[][]): unknown[] {
|
||||
return unique(newArr, []);
|
||||
}
|
||||
|
||||
export function toJsonString(value: unknown[]) {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
export function toInt() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function toFloat() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function toBoolean() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function toDateTime() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
average.doc = {
|
||||
name: 'average',
|
||||
description: 'Returns the mean average of all values in the array.',
|
||||
@@ -483,6 +503,14 @@ unique.doc = {
|
||||
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/arrays/#array-unique',
|
||||
};
|
||||
|
||||
toJsonString.doc = {
|
||||
name: 'toJsonString',
|
||||
description: 'Converts an array to a JSON string',
|
||||
docURL:
|
||||
'https://docs.n8n.io/code/builtin/data-transformation-functions/arrays/#array-toJsonString',
|
||||
returnType: 'string',
|
||||
};
|
||||
|
||||
export const arrayExtensions: ExtensionMap = {
|
||||
typeName: 'Array',
|
||||
functions: {
|
||||
@@ -506,5 +534,10 @@ export const arrayExtensions: ExtensionMap = {
|
||||
union,
|
||||
difference,
|
||||
intersection,
|
||||
toJsonString,
|
||||
toInt,
|
||||
toFloat,
|
||||
toBoolean,
|
||||
toDateTime,
|
||||
},
|
||||
};
|
||||
|
||||
35
packages/workflow/src/Extensions/BooleanExtensions.ts
Normal file
35
packages/workflow/src/Extensions/BooleanExtensions.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import type { ExtensionMap } from './Extensions';
|
||||
|
||||
export function toBoolean(value: boolean) {
|
||||
return value;
|
||||
}
|
||||
|
||||
export function toInt(value: boolean) {
|
||||
return value ? 1 : 0;
|
||||
}
|
||||
|
||||
export function toFloat(value: boolean) {
|
||||
return value ? 1 : 0;
|
||||
}
|
||||
|
||||
export function toDateTime() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
toInt.doc = {
|
||||
name: 'toInt',
|
||||
description: 'Converts a boolean to an integer. `false` is 0, `true` is 1.',
|
||||
section: 'cast',
|
||||
returnType: 'boolean',
|
||||
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/booleans/#boolean-toInt',
|
||||
};
|
||||
|
||||
export const booleanExtensions: ExtensionMap = {
|
||||
typeName: 'Boolean',
|
||||
functions: {
|
||||
toBoolean,
|
||||
toInt,
|
||||
toFloat,
|
||||
toDateTime,
|
||||
},
|
||||
};
|
||||
@@ -216,6 +216,25 @@ function plus(
|
||||
return DateTime.fromJSDate(date).plus(duration).toJSDate();
|
||||
}
|
||||
|
||||
function toDateTime(date: Date | DateTime): DateTime {
|
||||
if (isDateTime(date)) return date;
|
||||
|
||||
return DateTime.fromJSDate(date);
|
||||
}
|
||||
|
||||
function toInt(date: Date | DateTime): number {
|
||||
if (isDateTime(date)) {
|
||||
return date.toMillis();
|
||||
}
|
||||
return date.getTime();
|
||||
}
|
||||
|
||||
const toFloat = toInt;
|
||||
|
||||
function toBoolean() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
endOfMonth.doc = {
|
||||
name: 'endOfMonth',
|
||||
returnType: 'Date',
|
||||
@@ -267,7 +286,7 @@ format.doc = {
|
||||
description: 'Formats a Date in the given structure.',
|
||||
returnType: 'string',
|
||||
section: 'format',
|
||||
args: [{ name: 'fmt', type: 'TimeFormat' }],
|
||||
args: [{ name: 'fmt', default: 'yyyy-MM-dd', type: 'TimeFormat' }],
|
||||
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/dates/#date-format',
|
||||
};
|
||||
|
||||
@@ -295,6 +314,15 @@ isInLast.doc = {
|
||||
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/dates/#date-isInLast',
|
||||
};
|
||||
|
||||
toDateTime.doc = {
|
||||
name: 'toDateTime',
|
||||
description: 'Convert a JavaScript Date to a Luxon DateTime.',
|
||||
section: 'query',
|
||||
returnType: 'DateTime',
|
||||
hidden: true,
|
||||
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/dates/#date-toDateTime',
|
||||
};
|
||||
|
||||
minus.doc = {
|
||||
name: 'minus',
|
||||
description: 'Subtracts a given time period from a Date. Default unit is `milliseconds`.',
|
||||
@@ -332,5 +360,9 @@ export const dateExtensions: ExtensionMap = {
|
||||
minus,
|
||||
plus,
|
||||
format,
|
||||
toDateTime,
|
||||
toInt,
|
||||
toFloat,
|
||||
toBoolean,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -15,6 +15,7 @@ import type { ExpressionKind } from 'ast-types/gen/kinds';
|
||||
|
||||
import type { ExpressionChunk, ExpressionCode } from './ExpressionParser';
|
||||
import { joinExpression, splitExpression } from './ExpressionParser';
|
||||
import { booleanExtensions } from './BooleanExtensions';
|
||||
|
||||
const EXPRESSION_EXTENDER = 'extend';
|
||||
const EXPRESSION_EXTENDER_OPTIONAL = 'extendOptional';
|
||||
@@ -33,6 +34,7 @@ export const EXTENSION_OBJECTS = [
|
||||
numberExtensions,
|
||||
objectExtensions,
|
||||
stringExtensions,
|
||||
booleanExtensions,
|
||||
];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
@@ -48,6 +50,7 @@ const EXPRESSION_EXTENSION_METHODS = Array.from(
|
||||
...Object.keys(dateExtensions.functions),
|
||||
...Object.keys(arrayExtensions.functions),
|
||||
...Object.keys(objectExtensions.functions),
|
||||
...Object.keys(booleanExtensions.functions),
|
||||
...Object.keys(genericExtensions),
|
||||
]),
|
||||
);
|
||||
@@ -455,7 +458,7 @@ function findExtendedFunction(input: unknown, functionName: string): FoundFuncti
|
||||
let foundFunction: Function | undefined;
|
||||
if (Array.isArray(input)) {
|
||||
foundFunction = arrayExtensions.functions[functionName];
|
||||
} else if (isDate(input) && functionName !== 'toDate') {
|
||||
} else if (isDate(input) && functionName !== 'toDate' && functionName !== 'toDateTime') {
|
||||
// If it's a string date (from $json), convert it to a Date object,
|
||||
// unless that function is `toDate`, since `toDate` does something
|
||||
// very different on date objects
|
||||
@@ -469,6 +472,8 @@ function findExtendedFunction(input: unknown, functionName: string): FoundFuncti
|
||||
foundFunction = dateExtensions.functions[functionName];
|
||||
} else if (input !== null && typeof input === 'object') {
|
||||
foundFunction = objectExtensions.functions[functionName];
|
||||
} else if (typeof input === 'boolean') {
|
||||
foundFunction = booleanExtensions.functions[functionName];
|
||||
}
|
||||
|
||||
// Look for generic or builtin
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
import { DateTime } from 'luxon';
|
||||
import { ExpressionExtensionError } from '../errors/expression-extension.error';
|
||||
import type { ExtensionMap } from './Extensions';
|
||||
|
||||
@@ -40,6 +41,41 @@ function round(value: number, extraArgs: number[]) {
|
||||
return +value.toFixed(decimalPlaces);
|
||||
}
|
||||
|
||||
function toBoolean(value: number) {
|
||||
return value !== 0;
|
||||
}
|
||||
|
||||
function toInt(value: number) {
|
||||
return round(value, []);
|
||||
}
|
||||
|
||||
function toFloat(value: number) {
|
||||
return value;
|
||||
}
|
||||
|
||||
type DateTimeFormat = 'ms' | 's' | 'excel';
|
||||
function toDateTime(value: number, extraArgs: [DateTimeFormat]) {
|
||||
const [valueFormat = 'ms'] = extraArgs;
|
||||
|
||||
switch (valueFormat) {
|
||||
// Excel format is days since 1900
|
||||
// There is a bug where 1900 is incorrectly treated as a leap year
|
||||
case 'excel': {
|
||||
const DAYS_BETWEEN_1900_1970 = 25567;
|
||||
const DAYS_LEAP_YEAR_BUG_ADJUST = 2;
|
||||
const SECONDS_IN_DAY = 86_400;
|
||||
return DateTime.fromSeconds(
|
||||
(value - (DAYS_BETWEEN_1900_1970 + DAYS_LEAP_YEAR_BUG_ADJUST)) * SECONDS_IN_DAY,
|
||||
);
|
||||
}
|
||||
case 's':
|
||||
return DateTime.fromSeconds(value);
|
||||
case 'ms':
|
||||
default:
|
||||
return DateTime.fromMillis(value);
|
||||
}
|
||||
}
|
||||
|
||||
ceil.doc = {
|
||||
name: 'ceil',
|
||||
description: 'Rounds up a number to a whole number.',
|
||||
@@ -89,6 +125,26 @@ round.doc = {
|
||||
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/numbers/#number-round',
|
||||
};
|
||||
|
||||
toBoolean.doc = {
|
||||
name: 'toBoolean',
|
||||
description: 'Converts a number to a boolean. 0 is `false`, all other numbers are `true`.',
|
||||
section: 'cast',
|
||||
returnType: 'boolean',
|
||||
docURL:
|
||||
'https://docs.n8n.io/code/builtin/data-transformation-functions/numbers/#number-toBoolean',
|
||||
};
|
||||
|
||||
toDateTime.doc = {
|
||||
name: 'toDateTime',
|
||||
description:
|
||||
"Converts a number to a DateTime. Defaults to milliseconds. Format can be 'ms' (milliseconds), 's' (seconds) or 'excel' (Excel 1900 format).",
|
||||
section: 'cast',
|
||||
returnType: 'DateTime',
|
||||
args: [{ name: 'format?', type: 'string' }],
|
||||
docURL:
|
||||
'https://docs.n8n.io/code/builtin/data-transformation-functions/numbers/#number-toDateTime',
|
||||
};
|
||||
|
||||
export const numberExtensions: ExtensionMap = {
|
||||
typeName: 'Number',
|
||||
functions: {
|
||||
@@ -98,5 +154,9 @@ export const numberExtensions: ExtensionMap = {
|
||||
round,
|
||||
isEven,
|
||||
isOdd,
|
||||
toBoolean,
|
||||
toInt,
|
||||
toFloat,
|
||||
toDateTime,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -88,6 +88,26 @@ export function urlEncode(value: object) {
|
||||
return new URLSearchParams(value as Record<string, string>).toString();
|
||||
}
|
||||
|
||||
export function toJsonString(value: object) {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
export function toInt() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function toFloat() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function toBoolean() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function toDateTime() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
isEmpty.doc = {
|
||||
name: 'isEmpty',
|
||||
description: 'Checks if the Object has no key-value pairs.',
|
||||
@@ -168,6 +188,14 @@ values.doc = {
|
||||
returnType: 'Array',
|
||||
};
|
||||
|
||||
toJsonString.doc = {
|
||||
name: 'toJsonString',
|
||||
description: 'Converts an object to a JSON string',
|
||||
docURL:
|
||||
'https://docs.n8n.io/code/builtin/data-transformation-functions/objects/#object-toJsonString',
|
||||
returnType: 'string',
|
||||
};
|
||||
|
||||
export const objectExtensions: ExtensionMap = {
|
||||
typeName: 'Object',
|
||||
functions: {
|
||||
@@ -181,5 +209,10 @@ export const objectExtensions: ExtensionMap = {
|
||||
urlEncode,
|
||||
keys,
|
||||
values,
|
||||
toJsonString,
|
||||
toInt,
|
||||
toFloat,
|
||||
toBoolean,
|
||||
toDateTime,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import SHA from 'jssha';
|
||||
import MD5 from 'md5';
|
||||
import { encode } from 'js-base64';
|
||||
import { toBase64, fromBase64 } from 'js-base64';
|
||||
import { titleCase } from 'title-case';
|
||||
import type { Extension, ExtensionMap } from './Extensions';
|
||||
import { transliterate } from 'transliteration';
|
||||
import { ExpressionExtensionError } from '../errors/expression-extension.error';
|
||||
import type { DateTime } from 'luxon';
|
||||
import { tryToParseDateTime } from '../TypeValidation';
|
||||
|
||||
export const SupportedHashAlgorithms = [
|
||||
'md5',
|
||||
@@ -116,7 +118,7 @@ function hash(value: string, extraArgs: string[]): string {
|
||||
const algorithm = extraArgs[0]?.toLowerCase() ?? 'md5';
|
||||
switch (algorithm) {
|
||||
case 'base64':
|
||||
return encode(value);
|
||||
return toBase64(value);
|
||||
case 'md5':
|
||||
return MD5(value);
|
||||
case 'sha1':
|
||||
@@ -214,6 +216,14 @@ function toDate(value: string): Date {
|
||||
return date;
|
||||
}
|
||||
|
||||
function toDateTime(value: string): DateTime {
|
||||
try {
|
||||
return tryToParseDateTime(value);
|
||||
} catch (error) {
|
||||
throw new ExpressionExtensionError('cannot convert to Luxon DateTime');
|
||||
}
|
||||
}
|
||||
|
||||
function urlDecode(value: string, extraArgs: boolean[]): string {
|
||||
const [entireString = false] = extraArgs;
|
||||
if (entireString) {
|
||||
@@ -359,6 +369,37 @@ function extractUrl(value: string) {
|
||||
return matched[0];
|
||||
}
|
||||
|
||||
function extractUrlPath(value: string) {
|
||||
try {
|
||||
const url = new URL(value);
|
||||
return url.pathname;
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function parseJson(value: string): unknown {
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function toBoolean(value: string): boolean {
|
||||
const normalized = value.toLowerCase();
|
||||
const FALSY = new Set(['false', 'no', '0']);
|
||||
return normalized.length > 0 && !FALSY.has(normalized);
|
||||
}
|
||||
|
||||
function base64Encode(value: string): string {
|
||||
return toBase64(value);
|
||||
}
|
||||
|
||||
function base64Decode(value: string): string {
|
||||
return fromBase64(value);
|
||||
}
|
||||
|
||||
removeMarkdown.doc = {
|
||||
name: 'removeMarkdown',
|
||||
description: 'Removes Markdown formatting from a string.',
|
||||
@@ -382,9 +423,28 @@ toDate.doc = {
|
||||
description: 'Converts a string to a date.',
|
||||
section: 'cast',
|
||||
returnType: 'Date',
|
||||
hidden: true,
|
||||
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/strings/#string-toDate',
|
||||
};
|
||||
|
||||
toDateTime.doc = {
|
||||
name: 'toDateTime',
|
||||
description: 'Converts a string to a Luxon DateTime.',
|
||||
section: 'cast',
|
||||
returnType: 'DateTime',
|
||||
docURL:
|
||||
'https://docs.n8n.io/code/builtin/data-transformation-functions/strings/#string-toDateTime',
|
||||
};
|
||||
|
||||
toBoolean.doc = {
|
||||
name: 'toBoolean',
|
||||
description: 'Converts a string to a boolean.',
|
||||
section: 'cast',
|
||||
returnType: 'boolean',
|
||||
docURL:
|
||||
'https://docs.n8n.io/code/builtin/data-transformation-functions/strings/#string-toBoolean',
|
||||
};
|
||||
|
||||
toFloat.doc = {
|
||||
name: 'toFloat',
|
||||
description: 'Converts a string to a decimal number.',
|
||||
@@ -549,6 +609,15 @@ extractUrl.doc = {
|
||||
'https://docs.n8n.io/code/builtin/data-transformation-functions/strings/#string-extractUrl',
|
||||
};
|
||||
|
||||
extractUrlPath.doc = {
|
||||
name: 'extractUrlPath',
|
||||
description: 'Extracts the path from a URL. Returns undefined if none is found.',
|
||||
section: 'edit',
|
||||
returnType: 'string',
|
||||
docURL:
|
||||
'https://docs.n8n.io/code/builtin/data-transformation-functions/strings/#string-extractUrlPath',
|
||||
};
|
||||
|
||||
hash.doc = {
|
||||
name: 'hash',
|
||||
description: 'Returns a string hashed with the given algorithm. Default algorithm is `md5`.',
|
||||
@@ -567,6 +636,34 @@ quote.doc = {
|
||||
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/strings/#string-quote',
|
||||
};
|
||||
|
||||
parseJson.doc = {
|
||||
name: 'parseJson',
|
||||
description:
|
||||
'Parses a JSON string, constructing the JavaScript value or object described by the string.',
|
||||
section: 'cast',
|
||||
returnType: 'any',
|
||||
docURL:
|
||||
'https://docs.n8n.io/code/builtin/data-transformation-functions/strings/#string-parseJson',
|
||||
};
|
||||
|
||||
base64Encode.doc = {
|
||||
name: 'base64Encode',
|
||||
description: 'Converts a UTF-8-encoded string to a Base64 string.',
|
||||
section: 'edit',
|
||||
returnType: 'string',
|
||||
docURL:
|
||||
'https://docs.n8n.io/code/builtin/data-transformation-functions/strings/#string-base64Encode',
|
||||
};
|
||||
|
||||
base64Decode.doc = {
|
||||
name: 'base64Decode',
|
||||
description: 'Converts a Base64 string to a UTF-8 string.',
|
||||
section: 'edit',
|
||||
returnType: 'string',
|
||||
docURL:
|
||||
'https://docs.n8n.io/code/builtin/data-transformation-functions/strings/#string-base64Decode',
|
||||
};
|
||||
|
||||
const toDecimalNumber: Extension = toFloat.bind({});
|
||||
toDecimalNumber.doc = { ...toFloat.doc, hidden: true };
|
||||
const toWholeNumber: Extension = toInt.bind({});
|
||||
@@ -579,6 +676,8 @@ export const stringExtensions: ExtensionMap = {
|
||||
removeMarkdown,
|
||||
removeTags,
|
||||
toDate,
|
||||
toDateTime,
|
||||
toBoolean,
|
||||
toDecimalNumber,
|
||||
toFloat,
|
||||
toInt,
|
||||
@@ -600,5 +699,9 @@ export const stringExtensions: ExtensionMap = {
|
||||
extractEmail,
|
||||
extractDomain,
|
||||
extractUrl,
|
||||
extractUrlPath,
|
||||
parseJson,
|
||||
base64Encode,
|
||||
base64Decode,
|
||||
},
|
||||
};
|
||||
|
||||
16
packages/workflow/src/NativeMethods/Boolean.methods.ts
Normal file
16
packages/workflow/src/NativeMethods/Boolean.methods.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import type { NativeDoc } from '@/Extensions/Extensions';
|
||||
|
||||
export const booleanMethods: NativeDoc = {
|
||||
typeName: 'Boolean',
|
||||
functions: {
|
||||
toString: {
|
||||
doc: {
|
||||
name: 'toString',
|
||||
description: 'returns a string representing this boolean value.',
|
||||
docURL:
|
||||
'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean/toString',
|
||||
returnType: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -6,6 +6,7 @@ export const numberMethods: NativeDoc = {
|
||||
toFixed: {
|
||||
doc: {
|
||||
name: 'toFixed',
|
||||
hidden: true,
|
||||
description:
|
||||
'Formats a number using fixed-point notation. `digits` defaults to null if not given.',
|
||||
docURL:
|
||||
@@ -17,6 +18,7 @@ export const numberMethods: NativeDoc = {
|
||||
toPrecision: {
|
||||
doc: {
|
||||
name: 'toPrecision',
|
||||
hidden: true,
|
||||
description: 'Returns a string representing the number to the specified precision.',
|
||||
docURL:
|
||||
'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toPrecision',
|
||||
@@ -24,5 +26,14 @@ export const numberMethods: NativeDoc = {
|
||||
args: [{ name: 'precision?', type: 'number' }],
|
||||
},
|
||||
},
|
||||
toString: {
|
||||
doc: {
|
||||
name: 'toString',
|
||||
description: 'returns a string representing this number value.',
|
||||
docURL:
|
||||
'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toString',
|
||||
returnType: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -3,7 +3,14 @@ import { arrayMethods } from './Array.methods';
|
||||
import { numberMethods } from './Number.methods';
|
||||
import { objectMethods } from './Object.Methods';
|
||||
import type { NativeDoc } from '@/Extensions/Extensions';
|
||||
import { booleanMethods } from './Boolean.methods';
|
||||
|
||||
const NATIVE_METHODS: NativeDoc[] = [stringMethods, arrayMethods, numberMethods, objectMethods];
|
||||
const NATIVE_METHODS: NativeDoc[] = [
|
||||
stringMethods,
|
||||
arrayMethods,
|
||||
numberMethods,
|
||||
objectMethods,
|
||||
booleanMethods,
|
||||
];
|
||||
|
||||
export { NATIVE_METHODS as NativeMethods };
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
|
||||
import { arrayExtensions } from '../../src/Extensions/ArrayExtensions';
|
||||
import { evaluate } from './Helpers';
|
||||
|
||||
describe('Data Transformation Functions', () => {
|
||||
@@ -234,5 +235,27 @@ describe('Data Transformation Functions', () => {
|
||||
[16, 17, 18, 19, 20],
|
||||
]);
|
||||
});
|
||||
|
||||
test('.toJsonString() should work on an array', () => {
|
||||
expect(evaluate('={{ [true, 1, "one", {foo: "bar"}].toJsonString() }}')).toEqual(
|
||||
'[true,1,"one",{"foo":"bar"}]',
|
||||
);
|
||||
});
|
||||
|
||||
describe('Conversion methods', () => {
|
||||
test('should exist but return undefined (to not break expressions with mixed data)', () => {
|
||||
expect(evaluate('={{ numberList(1, 20).toInt() }}')).toBeUndefined();
|
||||
expect(evaluate('={{ numberList(1, 20).toFloat() }}')).toBeUndefined();
|
||||
expect(evaluate('={{ numberList(1, 20).toBoolean() }}')).toBeUndefined();
|
||||
expect(evaluate('={{ numberList(1, 20).toDateTime() }}')).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should not have a doc (hidden from autocomplete)', () => {
|
||||
expect(arrayExtensions.functions.toInt.doc).toBeUndefined();
|
||||
expect(arrayExtensions.functions.toFloat.doc).toBeUndefined();
|
||||
expect(arrayExtensions.functions.toBoolean.doc).toBeUndefined();
|
||||
expect(arrayExtensions.functions.toDateTime.doc).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
|
||||
import { booleanExtensions } from '../../src/Extensions/BooleanExtensions';
|
||||
import { evaluate } from './Helpers';
|
||||
|
||||
describe('Data Transformation Functions', () => {
|
||||
describe('Boolean Data Transformation Functions', () => {
|
||||
describe('Conversion methods', () => {
|
||||
describe('toInt/toFloat', () => {
|
||||
test('should return 1 for true, 0 for false', () => {
|
||||
expect(evaluate('={{ (true).toInt() }}')).toEqual(1);
|
||||
expect(evaluate('={{ (true).toFloat() }}')).toEqual(1);
|
||||
expect(evaluate('={{ (false).toInt() }}')).toEqual(0);
|
||||
expect(evaluate('={{ (false).toFloat() }}')).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toDateTime', () => {
|
||||
test('should return undefined', () => {
|
||||
expect(evaluate('={{ (true).toDateTime() }}')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('toBoolean', () => {
|
||||
test('should return itself', () => {
|
||||
expect(evaluate('={{ (true).toDateTime() }}')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
test('should not have a doc (hidden from autocomplete)', () => {
|
||||
expect(booleanExtensions.functions.toFloat.doc).toBeUndefined();
|
||||
expect(booleanExtensions.functions.toBoolean.doc).toBeUndefined();
|
||||
expect(booleanExtensions.functions.toDateTime.doc).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -5,6 +5,7 @@
|
||||
import { DateTime } from 'luxon';
|
||||
import { getGlobalState } from '@/GlobalState';
|
||||
import { evaluate, getLocalISOString } from './Helpers';
|
||||
import { dateExtensions } from '../../src/Extensions/DateExtensions';
|
||||
|
||||
const { defaultTimezone } = getGlobalState();
|
||||
|
||||
@@ -107,5 +108,56 @@ describe('Data Transformation Functions', () => {
|
||||
evaluate("={{ $now.isBetween($now, '2023-06-23', '2023-09-21'.toDate()) }}"),
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
describe('toDateTime', () => {
|
||||
test('should return itself for DateTime', () => {
|
||||
const result = evaluate(
|
||||
"={{ DateTime.fromFormat('01-01-2024', 'dd-MM-yyyy').toDateTime() }}",
|
||||
) as unknown as DateTime;
|
||||
expect(result).toBeInstanceOf(DateTime);
|
||||
expect(result.day).toEqual(1);
|
||||
expect(result.month).toEqual(1);
|
||||
expect(result.year).toEqual(2024);
|
||||
});
|
||||
|
||||
test('should return a DateTime for JS Date', () => {
|
||||
const result = evaluate(
|
||||
'={{ new Date(2024, 0, 1, 12).toDateTime() }}',
|
||||
) as unknown as DateTime;
|
||||
expect(result).toBeInstanceOf(DateTime);
|
||||
expect(result.day).toEqual(1);
|
||||
expect(result.month).toEqual(1);
|
||||
expect(result.year).toEqual(2024);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toInt/toFloat', () => {
|
||||
test('should return milliseconds for DateTime', () => {
|
||||
expect(evaluate("={{ DateTime.fromISO('2024-01-01T00:00:00.000Z').toInt() }}")).toEqual(
|
||||
1704067200000,
|
||||
);
|
||||
});
|
||||
|
||||
test('should return milliseconds for JS Date', () => {
|
||||
expect(evaluate('={{ new Date("2024-01-01T00:00:00.000Z").toFloat() }}')).toEqual(
|
||||
1704067200000,
|
||||
);
|
||||
});
|
||||
|
||||
test('should not have a doc (hidden from autocomplete)', () => {
|
||||
expect(dateExtensions.functions.toInt.doc).toBeUndefined();
|
||||
expect(dateExtensions.functions.toFloat.doc).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('toBoolean', () => {
|
||||
test('should return undefined', () => {
|
||||
expect(evaluate('={{ new Date("2024-01-01T00:00:00.000Z").toBoolean() }}')).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should not have a doc (hidden from autocomplete)', () => {
|
||||
expect(dateExtensions.functions.toBoolean.doc).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -55,6 +55,52 @@ describe('Data Transformation Functions', () => {
|
||||
expect(() => evaluate('={{ (NaN).isEven() }}')).toThrow();
|
||||
expect(() => evaluate('={{ (9.2).isEven() }}')).toThrow();
|
||||
});
|
||||
|
||||
describe('toDateTime', () => {
|
||||
test('from milliseconds (default)', () => {
|
||||
expect(evaluate('={{ (1704085200000).toDateTime().toISO() }}')).toEqual(
|
||||
'2024-01-01T00:00:00.000-05:00',
|
||||
);
|
||||
expect(evaluate('={{ (1704085200000).toDateTime("ms").toISO() }}')).toEqual(
|
||||
'2024-01-01T00:00:00.000-05:00',
|
||||
);
|
||||
});
|
||||
|
||||
test('from seconds', () => {
|
||||
expect(evaluate('={{ (1704085200).toDateTime("s").toISO() }}')).toEqual(
|
||||
'2024-01-01T00:00:00.000-05:00',
|
||||
);
|
||||
});
|
||||
|
||||
test('from Excel 1900 format', () => {
|
||||
expect(evaluate('={{ (42144).toDateTime("excel").toISO() }}')).toEqual(
|
||||
'2015-05-19T20:00:00.000-04:00',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toInt', () => {
|
||||
test('should round numbers', () => {
|
||||
expect(evaluate('={{ (42144).toInt() }}')).toEqual(42144);
|
||||
expect(evaluate('={{ (42144.345).toInt() }}')).toEqual(42144);
|
||||
expect(evaluate('={{ (42144.545).toInt() }}')).toEqual(42145);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toFloat', () => {
|
||||
test('should return itself', () => {
|
||||
expect(evaluate('={{ (42144).toFloat() }}')).toEqual(42144);
|
||||
expect(evaluate('={{ (42144.345).toFloat() }}')).toEqual(42144.345);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toBoolean', () => {
|
||||
test('should return false for 0, 1 for other numbers', () => {
|
||||
expect(evaluate('={{ (42144).toBoolean() }}')).toBe(true);
|
||||
expect(evaluate('={{ (-1.549).toBoolean() }}')).toBe(true);
|
||||
expect(evaluate('={{ (0).toBoolean() }}')).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Multiple expressions', () => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { objectExtensions } from '../../src/Extensions/ObjectExtensions';
|
||||
import { evaluate } from './Helpers';
|
||||
|
||||
describe('Data Transformation Functions', () => {
|
||||
@@ -89,5 +90,27 @@ describe('Data Transformation Functions', () => {
|
||||
test('.values should work on an object', () => {
|
||||
expect(evaluate('={{ ({ test1: 1, test2: "2" }).values() }}')).toEqual([1, '2']);
|
||||
});
|
||||
|
||||
test('.toJsonString() should work on an object', () => {
|
||||
expect(evaluate('={{ ({ test1: 1, test2: "2" }).toJsonString() }}')).toEqual(
|
||||
'{"test1":1,"test2":"2"}',
|
||||
);
|
||||
});
|
||||
|
||||
describe('Conversion methods', () => {
|
||||
test('should exist but return undefined (to not break expressions with mixed data)', () => {
|
||||
expect(evaluate('={{ ({ test1: 1, test2: "2" }).toInt() }}')).toBeUndefined();
|
||||
expect(evaluate('={{ ({ test1: 1, test2: "2" }).toFloat() }}')).toBeUndefined();
|
||||
expect(evaluate('={{ ({ test1: 1, test2: "2" }).toBoolean() }}')).toBeUndefined();
|
||||
expect(evaluate('={{ ({ test1: 1, test2: "2" }).toDateTime() }}')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should not have a doc (hidden from autocomplete)', () => {
|
||||
expect(objectExtensions.functions.toInt.doc).toBeUndefined();
|
||||
expect(objectExtensions.functions.toFloat.doc).toBeUndefined();
|
||||
expect(objectExtensions.functions.toBoolean.doc).toBeUndefined();
|
||||
expect(objectExtensions.functions.toDateTime.doc).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
import { DateTime } from 'luxon';
|
||||
import { evaluate } from './Helpers';
|
||||
import { ExpressionExtensionError } from '../../src/errors';
|
||||
|
||||
describe('Data Transformation Functions', () => {
|
||||
describe('String Data Transformation Functions', () => {
|
||||
@@ -244,5 +246,48 @@ describe('Data Transformation Functions', () => {
|
||||
expect(evaluate('={{ "aaaaaaaa".isEmail() }}')).toEqual(false);
|
||||
expect(evaluate('={{ "test @ n8n".isEmail() }}')).toEqual(false);
|
||||
});
|
||||
|
||||
test('.toDateTime should work on a variety of formats', () => {
|
||||
expect(evaluate('={{ "Wed, 21 Oct 2015 07:28:00 GMT".toDateTime() }}')).toBeInstanceOf(
|
||||
DateTime,
|
||||
);
|
||||
expect(evaluate('={{ "2008-11-11".toDateTime() }}')).toBeInstanceOf(DateTime);
|
||||
expect(evaluate('={{ "1-Feb-2024".toDateTime() }}')).toBeInstanceOf(DateTime);
|
||||
expect(() => evaluate('={{ "hi".toDateTime() }}')).toThrowError(
|
||||
new ExpressionExtensionError('cannot convert to Luxon DateTime'),
|
||||
);
|
||||
});
|
||||
|
||||
test('.extractUrlPath should work on a string', () => {
|
||||
expect(
|
||||
evaluate('={{ "https://example.com/orders/1/detail#hash?foo=bar".extractUrlPath() }}'),
|
||||
).toEqual('/orders/1/detail');
|
||||
expect(evaluate('={{ "hi".extractUrlPath() }}')).toBeUndefined();
|
||||
});
|
||||
|
||||
test('.parseJson should work on a string', () => {
|
||||
expect(evaluate('={{ \'{"test1":1,"test2":"2"}\'.parseJson() }}')).toEqual({
|
||||
test1: 1,
|
||||
test2: '2',
|
||||
});
|
||||
expect(evaluate('={{ "hi".parseJson() }}')).toBeUndefined();
|
||||
});
|
||||
|
||||
test('.toBoolean should work on a string', () => {
|
||||
expect(evaluate('={{ "False".toBoolean() }}')).toBe(false);
|
||||
expect(evaluate('={{ "".toBoolean() }}')).toBe(false);
|
||||
expect(evaluate('={{ "0".toBoolean() }}')).toBe(false);
|
||||
expect(evaluate('={{ "no".toBoolean() }}')).toBe(false);
|
||||
expect(evaluate('={{ "TRUE".toBoolean() }}')).toBe(true);
|
||||
expect(evaluate('={{ "hello".toBoolean() }}')).toBe(true);
|
||||
});
|
||||
|
||||
test('.base64Encode should work on a string', () => {
|
||||
expect(evaluate('={{ "n8n test".base64Encode() }}')).toBe('bjhuIHRlc3Q=');
|
||||
});
|
||||
|
||||
test('.base64Decode should work on a string', () => {
|
||||
expect(evaluate('={{ "bjhuIHRlc3Q=".base64Decode() }}')).toBe('n8n test');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user