first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,274 @@
|
||||
// @vitest-environment jsdom
|
||||
|
||||
import { evaluate } from './helpers';
|
||||
import { arrayExtensions } from '../../src/extensions/array-extensions';
|
||||
|
||||
describe('Data Transformation Functions', () => {
|
||||
describe('Array Data Transformation Functions', () => {
|
||||
test('.randomItem() should work correctly on an array', () => {
|
||||
expect(evaluate('={{ [1,2,3].randomItem() }}')).not.toBeUndefined();
|
||||
});
|
||||
|
||||
test('.isNotEmpty() should work correctly on an array', () => {
|
||||
expect(evaluate('={{ [1,2,3, "imhere"].isNotEmpty() }}')).toEqual(true);
|
||||
});
|
||||
|
||||
test('.pluck() should work correctly on an array', () => {
|
||||
expect(
|
||||
evaluate(`={{ [
|
||||
{ value: 1, string: '1' },
|
||||
{ value: 2, string: '2' },
|
||||
{ value: 3, string: '3' },
|
||||
{ value: 4, string: '4' },
|
||||
{ value: 5, string: '5' },
|
||||
{ value: 6, string: '6' },
|
||||
{ value: { something: 'else' } }
|
||||
].pluck("value") }}`),
|
||||
).toEqual(expect.arrayContaining([1, 2, 3, 4, 5, 6, { something: 'else' }]));
|
||||
});
|
||||
|
||||
test('.pluck() should work correctly for multiple values', () => {
|
||||
expect(
|
||||
evaluate(`={{ [
|
||||
{
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
phone: {
|
||||
home: '111-222',
|
||||
office: '333-444'
|
||||
}
|
||||
},
|
||||
{
|
||||
firstName: 'Jane',
|
||||
lastName: 'Doe',
|
||||
phone: {
|
||||
office: '555-666'
|
||||
}
|
||||
}
|
||||
].pluck("firstName", "lastName") }}`),
|
||||
).toEqual(
|
||||
expect.arrayContaining([
|
||||
['John', 'Doe'],
|
||||
['Jane', 'Doe'],
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
test('.pluck() should work return everything with no args', () => {
|
||||
expect(
|
||||
evaluate(`={{ [
|
||||
{ value: 1, string: '1' },
|
||||
{ value: 2, string: '2' },
|
||||
{ value: 3, string: '3' },
|
||||
{ value: 4, string: '4' },
|
||||
{ value: 5, string: '5' },
|
||||
{ value: 6, string: '6' },
|
||||
{ value: { something: 'else' } }
|
||||
].pluck() }}`),
|
||||
).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ value: 1, string: '1' },
|
||||
{ value: 2, string: '2' },
|
||||
{ value: 3, string: '3' },
|
||||
{ value: 4, string: '4' },
|
||||
{ value: 5, string: '5' },
|
||||
{ value: 6, string: '6' },
|
||||
{ value: { something: 'else' } },
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
test('.unique() should work correctly on an array', () => {
|
||||
expect(evaluate('={{ ["repeat","repeat","a","b","c"].unique() }}')).toEqual(
|
||||
expect.arrayContaining(['repeat', 'repeat', 'a', 'b', 'c']),
|
||||
);
|
||||
});
|
||||
|
||||
test('.unique() should work on an arrays containing nulls, objects and arrays', () => {
|
||||
expect(
|
||||
evaluate('={{ [1, 2, 3, "as", {}, {}, 1, 2, [1,2], "[sad]", "[sad]", null].unique() }}'),
|
||||
).toEqual([1, 2, 3, 'as', {}, [1, 2], '[sad]', null]);
|
||||
});
|
||||
|
||||
test('.unique() should work on an arrays of objects', () => {
|
||||
expect(
|
||||
evaluate(
|
||||
"={{ [{'name':'Nathan', age:42}, {'name':'Jan', age:16}, {'name':'Nathan', age:21}].unique('name') }}",
|
||||
),
|
||||
).toEqual([
|
||||
{ name: 'Nathan', age: 42 },
|
||||
{ name: 'Jan', age: 16 },
|
||||
]);
|
||||
});
|
||||
|
||||
test('.isEmpty() should work correctly on an array', () => {
|
||||
expect(evaluate('={{ [].isEmpty() }}')).toEqual(true);
|
||||
});
|
||||
|
||||
test('.isEmpty() should work correctly on an array', () => {
|
||||
expect(evaluate('={{ [1].isEmpty() }}')).toEqual(false);
|
||||
});
|
||||
|
||||
test('.last() should work correctly on an array', () => {
|
||||
expect(evaluate('={{ ["repeat","repeat","a","b","c"].last() }}')).toEqual('c');
|
||||
});
|
||||
|
||||
test('.first() should work correctly on an array', () => {
|
||||
expect(evaluate('={{ ["repeat","repeat","a","b","c"].first() }}')).toEqual('repeat');
|
||||
});
|
||||
|
||||
test('.merge() should work correctly on an array', () => {
|
||||
expect(
|
||||
evaluate(
|
||||
'={{ [{ test1: 1, test2: 2 }, { test1: 1, test3: 3 }].merge([{ test1: 2, test3: 3 }, { test4: 4 }]) }}',
|
||||
),
|
||||
).toEqual({ test1: 1, test2: 2, test3: 3, test4: 4 });
|
||||
});
|
||||
|
||||
test('.merge() should work correctly without arguments', () => {
|
||||
expect(
|
||||
evaluate(
|
||||
'={{ [{ a: 1, some: null }, { a: 2, c: "something" }, 2, "asds", { b: 23 }, null, [1, 2]].merge() }}',
|
||||
),
|
||||
).toEqual({ a: 1, some: null, c: 'something', b: 23 });
|
||||
});
|
||||
|
||||
test('.smartJoin() should work correctly on an array of objects', () => {
|
||||
expect(
|
||||
evaluate(
|
||||
'={{ [{ name: "test1", value: "value1" }, { name: "test2", value: null }].smartJoin("name", "value") }}',
|
||||
),
|
||||
).toEqual({
|
||||
test1: 'value1',
|
||||
test2: null,
|
||||
});
|
||||
});
|
||||
|
||||
test('.renameKeys() should work correctly on an array of objects', () => {
|
||||
expect(
|
||||
evaluate(
|
||||
'={{ [{ test1: 1, test2: 2 }, { test1: 1, test3: 3 }].renameKeys("test1", "rename1", "test3", "rename3") }}',
|
||||
),
|
||||
).toEqual([
|
||||
{ rename1: 1, test2: 2 },
|
||||
{ rename1: 1, rename3: 3 },
|
||||
]);
|
||||
});
|
||||
|
||||
test('.sum() should work on an array of numbers', () => {
|
||||
expect(evaluate('={{ [1, 2, 3, 4, 5, 6].sum() }}')).toEqual(21);
|
||||
expect(() => evaluate('={{ ["1", 2, 3, 4, 5, "bad"].sum() }}')).toThrow();
|
||||
});
|
||||
|
||||
test('.average() should work on an array of numbers', () => {
|
||||
expect(evaluate('={{ [1, 2, 3, 4, 5, 6].average() }}')).toEqual(3.5);
|
||||
expect(() => evaluate('={{ ["1", 2, 3, 4, 5, "bad"].average() }}')).toThrow();
|
||||
});
|
||||
|
||||
test('.min() should work on an array of numbers', () => {
|
||||
expect(evaluate('={{ [1, 2, 3, 4, 5, 6].min() }}')).toEqual(1);
|
||||
expect(() => evaluate('={{ ["1", 2, 3, 4, 5, "bad"].min() }}')).toThrow();
|
||||
});
|
||||
|
||||
test('.max() should work on an array of numbers', () => {
|
||||
expect(evaluate('={{ [1, 2, 3, 4, 5, 6].max() }}')).toEqual(6);
|
||||
expect(() => evaluate('={{ ["1", 2, 3, 4, 5, "bad"].max() }}')).toThrow();
|
||||
});
|
||||
|
||||
test('.union() should work on an array of objects', () => {
|
||||
expect(
|
||||
evaluate(
|
||||
'={{ [{ test1: 1 }, { test2: 2 }].union([{ test1: 1, test3: 3 }, { test2: 2 }, { test4: 4 }]) }}',
|
||||
),
|
||||
).toEqual([{ test1: 1 }, { test2: 2 }, { test1: 1, test3: 3 }, { test4: 4 }]);
|
||||
});
|
||||
|
||||
test('.union() should work on an arrays containing nulls, objects and arrays', () => {
|
||||
expect(evaluate('={{ [1, 2, "dd", {}, null].union([1, {}, null, 3]) }}')).toEqual([
|
||||
1,
|
||||
2,
|
||||
'dd',
|
||||
{},
|
||||
null,
|
||||
3,
|
||||
]);
|
||||
});
|
||||
|
||||
test('.intersection() should work on an array of objects', () => {
|
||||
expect(
|
||||
evaluate(
|
||||
'={{ [{ test1: 1 }, { test2: 2 }].intersection([{ test1: 1, test3: 3 }, { test2: 2 }, { test4: 4 }]) }}',
|
||||
),
|
||||
).toEqual([{ test2: 2 }]);
|
||||
});
|
||||
|
||||
test('.intersection() should work on an arrays containing nulls, objects and arrays', () => {
|
||||
expect(evaluate('={{ [1, 2, "dd", {}, null].intersection([1, {}, null]) }}')).toEqual([
|
||||
1,
|
||||
{},
|
||||
null,
|
||||
]);
|
||||
});
|
||||
|
||||
test('.difference() should work on an array of objects', () => {
|
||||
expect(
|
||||
evaluate(
|
||||
'={{ [{ test1: 1 }, { test2: 2 }].difference([{ test1: 1, test3: 3 }, { test2: 2 }, { test4: 4 }]) }}',
|
||||
),
|
||||
).toEqual([{ test1: 1 }]);
|
||||
|
||||
expect(
|
||||
evaluate('={{ [{ test1: 1 }, { test2: 2 }].difference([{ test1: 1 }, { test2: 2 }]) }}'),
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
test('.difference() should work on an arrays containing nulls, objects and arrays', () => {
|
||||
expect(
|
||||
evaluate('={{ [1, 2, "dd", {}, null, ["a", 1]].difference([1, {}, null, ["a", 1]]) }}'),
|
||||
).toEqual([2, 'dd']);
|
||||
});
|
||||
|
||||
test('.compact() should work on an array', () => {
|
||||
expect(
|
||||
evaluate(
|
||||
'={{ [{ test1: 1, test2: undefined, test3: null }, null, undefined, 1, 2, 0, { test: "asdf" }].compact() }}',
|
||||
),
|
||||
).toEqual([{ test1: 1 }, 1, 2, 0, { test: 'asdf' }]);
|
||||
});
|
||||
|
||||
test('.chunk() should work on an array', () => {
|
||||
expect(evaluate('={{ numberList(1, 20).chunk(5) }}')).toEqual([
|
||||
[1, 2, 3, 4, 5],
|
||||
[6, 7, 8, 9, 10],
|
||||
[11, 12, 13, 14, 15],
|
||||
[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"}]',
|
||||
);
|
||||
});
|
||||
|
||||
test('.append() should work on an array', () => {
|
||||
expect(evaluate('={{ [1,2,3].append(4,5,"done") }}')).toEqual([1, 2, 3, 4, 5, 'done']);
|
||||
});
|
||||
|
||||
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,37 @@
|
||||
// @vitest-environment jsdom
|
||||
|
||||
import { evaluate } from './helpers';
|
||||
import { booleanExtensions } from '../../src/extensions/boolean-extensions';
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,379 @@
|
||||
// @vitest-environment jsdom
|
||||
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { evaluate, getLocalISOString } from './helpers';
|
||||
import { dateExtensions } from '../../src/extensions/date-extensions';
|
||||
import { getGlobalState } from '../../src/global-state';
|
||||
|
||||
const { defaultTimezone } = getGlobalState();
|
||||
|
||||
describe('Data Transformation Functions', () => {
|
||||
describe('Date Data Transformation Functions', () => {
|
||||
test('.isWeekend() should work correctly on a date', () => {
|
||||
expect(evaluate('={{ DateTime.local(2023, 1, 20).isWeekend() }}')).toBe(false);
|
||||
expect(evaluate('={{ DateTime.local(2023, 1, 21).isWeekend() }}')).toBe(true);
|
||||
expect(evaluate('={{ DateTime.local(2023, 1, 22).isWeekend() }}')).toBe(true);
|
||||
expect(evaluate('={{ DateTime.local(2023, 1, 23).isWeekend() }}')).toBe(false);
|
||||
});
|
||||
|
||||
describe('.beginningOf', () => {
|
||||
test('.beginningOf("week") should work correctly on a date', () => {
|
||||
expect(evaluate('={{ DateTime.local(2023, 1, 20).beginningOf("week") }}')).toEqual(
|
||||
DateTime.local(2023, 1, 16, { zone: defaultTimezone }),
|
||||
);
|
||||
|
||||
expect(evaluate('={{ new Date(2023, 0, 20).beginningOf("week") }}')).toEqual(
|
||||
DateTime.local(2023, 1, 16, { zone: defaultTimezone }).toJSDate(),
|
||||
);
|
||||
});
|
||||
|
||||
test('.beginningOf("week") should work correctly on a string', () => {
|
||||
const evaluatedDate = evaluate('={{ "2023-01-30".toDate().beginningOf("week") }}');
|
||||
const expectedDate = DateTime.local(2023, 1, 23, { zone: defaultTimezone }).toJSDate();
|
||||
|
||||
if (evaluatedDate && evaluatedDate instanceof Date) {
|
||||
expect(evaluatedDate.toDateString()).toEqual(expectedDate.toDateString());
|
||||
}
|
||||
});
|
||||
|
||||
test('.beginningOf("month") should work correctly on a string', () => {
|
||||
const evaluatedDate = evaluate('={{ "2023-06-16".toDate().beginningOf("month") }}');
|
||||
const expectedDate = DateTime.local(2023, 6, 1, { zone: defaultTimezone }).toJSDate();
|
||||
|
||||
if (evaluatedDate && evaluatedDate instanceof Date) {
|
||||
expect(evaluatedDate.toDateString()).toEqual(expectedDate.toDateString());
|
||||
}
|
||||
});
|
||||
|
||||
test('.beginningOf("year") should work correctly on a string', () => {
|
||||
const evaluatedDate = evaluate('={{ "2023-01-30".toDate().beginningOf("year") }}');
|
||||
const expectedDate = DateTime.local(2023, 1, 1, { zone: defaultTimezone }).toJSDate();
|
||||
|
||||
if (evaluatedDate && evaluatedDate instanceof Date) {
|
||||
expect(evaluatedDate.toDateString()).toEqual(expectedDate.toDateString());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('.endOfMonth() should work correctly on a date', () => {
|
||||
expect(evaluate('={{ DateTime.local(2023, 1, 16).endOfMonth() }}')).toEqual(
|
||||
DateTime.local(2023, 1, 31, 23, 59, 59, 999, { zone: defaultTimezone }),
|
||||
);
|
||||
expect(evaluate('={{ new Date(2023, 0, 16).endOfMonth() }}')).toEqual(
|
||||
DateTime.local(2023, 1, 31, 23, 59, 59, 999, { zone: defaultTimezone }).toJSDate(),
|
||||
);
|
||||
});
|
||||
|
||||
describe('.extract', () => {
|
||||
test('.extract("day") should work correctly on a date', () => {
|
||||
expect(evaluate('={{ DateTime.local(2023, 1, 20).extract("day") }}')).toEqual(20);
|
||||
});
|
||||
|
||||
test('should extract year from a date', () => {
|
||||
expect(evaluate('={{ new Date("2024-03-30T18:49").extract("year") }}')).toEqual(2024);
|
||||
});
|
||||
|
||||
test('should extract yearDayNumber from a date', () => {
|
||||
expect(evaluate('={{ new Date("2024-03-30T18:49").extract("yearDayNumber") }}')).toEqual(
|
||||
90,
|
||||
);
|
||||
});
|
||||
|
||||
test('should extract month from a date', () => {
|
||||
expect(evaluate('={{ new Date("2024-03-30T18:49").extract("month") }}')).toEqual(3);
|
||||
});
|
||||
|
||||
test('should extract week from a date', () => {
|
||||
expect(evaluate('={{ DateTime.local(2023, 1, 20).extract() }}')).toEqual(3);
|
||||
expect(evaluate('={{ DateTime.local(2023, 1, 20).extract("week") }}')).toEqual(3);
|
||||
});
|
||||
|
||||
test('should extract day from a date', () => {
|
||||
expect(evaluate('={{ DateTime.fromISO("2024-03-30T18:49").extract("day") }}')).toEqual(30);
|
||||
});
|
||||
|
||||
test('should extract hour from a date', () => {
|
||||
expect(evaluate('={{ DateTime.fromISO("2024-03-30T18:49").extract("hour") }}')).toEqual(18);
|
||||
});
|
||||
|
||||
test('should extract minute from a date', () => {
|
||||
expect(evaluate('={{ DateTime.fromISO("2024-03-30T18:49").extract("minute") }}')).toEqual(
|
||||
49,
|
||||
);
|
||||
});
|
||||
|
||||
test('should extract second from a date', () => {
|
||||
expect(evaluate('={{ DateTime.fromISO("2024-03-30T18:49").extract("second") }}')).toEqual(
|
||||
0,
|
||||
);
|
||||
});
|
||||
|
||||
test('should extract millisecond from a date', () => {
|
||||
expect(
|
||||
evaluate('={{ DateTime.fromISO("2024-03-30T18:49:00.123Z").extract("millisecond") }}'),
|
||||
).toEqual(123);
|
||||
});
|
||||
|
||||
test('should return undefined for invalid unit', () => {
|
||||
expect(evaluate('={{ DateTime.fromISO("2024-03-30T18:49").extract("invalid") }}')).toBe(
|
||||
null,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('.format', () => {
|
||||
test('should format date with custom format', () => {
|
||||
expect(
|
||||
evaluate('={{ DateTime.fromISO("2024-03-30T18:49").format("yyyy LLL dd") }}'),
|
||||
).toEqual('2024 Mar 30');
|
||||
});
|
||||
|
||||
test('should format date with ISO format', () => {
|
||||
expect(
|
||||
evaluate('={{ DateTime.fromISO("2024-03-30T18:49").format("yyyy-MM-dd\'T\'HH:mm:ss") }}'),
|
||||
).toEqual('2024-03-30T18:49:00');
|
||||
});
|
||||
});
|
||||
|
||||
test('.toDate() should work on a string', () => {
|
||||
const date = new Date(2022, 0, 3);
|
||||
expect(evaluate(`={{ "${getLocalISOString(date)}".toDate() }}`)).toEqual(date);
|
||||
});
|
||||
|
||||
describe('.inBetween', () => {
|
||||
test('should work on string and Date', () => {
|
||||
expect(
|
||||
evaluate("={{ $now.isBetween('2023-06-23'.toDate(), '2023-06-23') }}"),
|
||||
).toBeDefined();
|
||||
});
|
||||
|
||||
test('should work on string and DateTime', () => {
|
||||
expect(evaluate("={{ $now.isBetween($now, '2023-06-23') }}")).toBeDefined();
|
||||
});
|
||||
|
||||
test('should not work for invalid strings', () => {
|
||||
expect(evaluate("={{ $now.isBetween($now, 'invalid') }}")).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should not work for numbers', () => {
|
||||
expect(evaluate('={{ $now.isBetween($now, 1) }}')).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should not work for a single argument', () => {
|
||||
expect(() => evaluate('={{ $now.isBetween($now) }}')).toThrow();
|
||||
});
|
||||
|
||||
test('should not work for a more than two arguments', () => {
|
||||
expect(() =>
|
||||
evaluate("={{ $now.isBetween($now, '2023-06-23', '2023-09-21'.toDate()) }}"),
|
||||
).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('.diffTo', () => {
|
||||
test('should work with a single unit', () => {
|
||||
expect(
|
||||
evaluate(
|
||||
"={{ '2025-01-01'.toDateTime().diffTo('2024-03-30T18:49:07.234', 'days').floor() }}",
|
||||
),
|
||||
).toEqual(276);
|
||||
});
|
||||
|
||||
test('should work with an array of units', () => {
|
||||
expect(
|
||||
evaluate(
|
||||
"={{ '2025-01-01T00:00:00.000'.toDateTime().diffTo('2024-03-30T18:49:07.234', ['months', 'days']) }}",
|
||||
),
|
||||
).toEqual({ months: 9, days: 1.2158884953703704 });
|
||||
});
|
||||
|
||||
test('should return difference in days', () => {
|
||||
expect(
|
||||
evaluate(
|
||||
'={{ DateTime.fromISO("2024-03-30T18:49:00Z").diffTo("2024-03-25T18:49:00Z", "days") }}',
|
||||
),
|
||||
).toEqual(5);
|
||||
});
|
||||
|
||||
test('should return difference in hours', () => {
|
||||
expect(
|
||||
evaluate(
|
||||
'={{ DateTime.fromISO("2024-03-30T18:49:00Z").diffTo("2024-03-30T12:49:00Z", "hours") }}',
|
||||
),
|
||||
).toEqual(6);
|
||||
});
|
||||
|
||||
test('should return difference in minutes', () => {
|
||||
expect(
|
||||
evaluate(
|
||||
'={{ DateTime.fromISO("2024-03-30T18:49:00Z").diffTo("2024-03-30T18:44:00Z", "minutes") }}',
|
||||
),
|
||||
).toEqual(5);
|
||||
});
|
||||
|
||||
test('should return difference in seconds', () => {
|
||||
expect(
|
||||
evaluate(
|
||||
'={{ DateTime.fromISO("2024-03-30T18:49:00Z").diffTo("2024-03-30T18:48:55Z", "seconds") }}',
|
||||
),
|
||||
).toEqual(5);
|
||||
});
|
||||
|
||||
test('should return difference in milliseconds', () => {
|
||||
expect(
|
||||
evaluate(
|
||||
'={{ DateTime.fromISO("2024-03-30T18:49:00.500Z").diffTo("2024-03-30T18:49:00.000Z", "milliseconds") }}',
|
||||
),
|
||||
).toEqual(500);
|
||||
});
|
||||
|
||||
test('should throw for invalid unit', () => {
|
||||
expect(() =>
|
||||
evaluate(
|
||||
'={{ DateTime.fromISO("2024-03-30T18:49:00Z").diffTo("2024-03-30T18:49:00Z", "invalid") }}',
|
||||
),
|
||||
).toThrow('Unsupported unit');
|
||||
});
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
describe('.isInLast', () => {
|
||||
it('should return true if the date is within the last n minutes', () => {
|
||||
expect(
|
||||
evaluate(
|
||||
`={{ new Date("${DateTime.now().minus({ minutes: 5 }).toISO()}").isInLast(10, "minutes") }}`,
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if the date is not within the last n minutes', () => {
|
||||
expect(
|
||||
evaluate(
|
||||
`={{ new Date("${DateTime.now().minus({ minutes: 15 }).toISO()}").isInLast(10, "minutes") }}`,
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle default unit as minutes', () => {
|
||||
expect(
|
||||
evaluate(
|
||||
`={{ new Date("${DateTime.now().minus({ minutes: 5 }).toISO()}").isInLast(10) }}`,
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('.minus', () => {
|
||||
it('should subtract days from the date', () => {
|
||||
expect(evaluate('={{ new Date("2024-03-30T18:49:00Z").minus(7, "days") }}')).toEqual(
|
||||
new Date('2024-03-23T18:49:00.000Z'),
|
||||
);
|
||||
});
|
||||
|
||||
it('should subtract years from the date', () => {
|
||||
expect(evaluate('={{ new Date("2024-03-30T18:49:00Z").minus(4, "years") }}')).toEqual(
|
||||
new Date('2020-03-30T18:49:00.000Z'),
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle default unit as milliseconds', () => {
|
||||
expect(evaluate('={{ new Date("2024-03-30T18:49:00Z").minus(1000) }}')).toEqual(
|
||||
new Date('2024-03-30T18:48:59.000Z'),
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle DateTime instances', () => {
|
||||
expect(
|
||||
evaluate('={{ DateTime.fromISO("2024-03-30T18:49:00Z").minus(1, "day").toJSDate() }}'),
|
||||
).toEqual(new Date('2024-03-29T18:49:00.000Z'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('.plus', () => {
|
||||
it('should subtract days from the date', () => {
|
||||
expect(evaluate('={{ new Date("2024-03-30T18:49:00Z").plus(7, "days") }}')).toEqual(
|
||||
new Date('2024-04-06T18:49:00.000Z'),
|
||||
);
|
||||
});
|
||||
|
||||
it('should subtract years from the date', () => {
|
||||
expect(evaluate('={{ new Date("2024-03-30T18:49:00Z").plus(4, "years") }}')).toEqual(
|
||||
new Date('2028-03-30T18:49:00.000Z'),
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle default unit as milliseconds', () => {
|
||||
expect(evaluate('={{ new Date("2024-03-30T18:49:00Z").plus(1000) }}')).toEqual(
|
||||
new Date('2024-03-30T18:49:01.000Z'),
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle DateTime instances', () => {
|
||||
expect(
|
||||
evaluate('={{ DateTime.fromISO("2024-03-30T18:49:00Z").plus(1, "day").toJSDate() }}'),
|
||||
).toEqual(new Date('2024-03-31T18:49:00.000Z'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('.isDst', () => {
|
||||
test('should return true for a date in DST', () => {
|
||||
expect(evaluate('={{ DateTime.fromISO("2024-06-30T18:49:00Z").isDst() }}')).toBe(true);
|
||||
});
|
||||
|
||||
test('should return false for a date not in DST', () => {
|
||||
expect(evaluate('={{ DateTime.fromISO("2024-01-30T18:49:00Z").isDst() }}')).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,266 @@
|
||||
// @vitest-environment jsdom
|
||||
|
||||
/* eslint-disable n8n-local-rules/no-interpolation-in-regular-string */
|
||||
|
||||
import { evaluate } from './helpers';
|
||||
import { ExpressionExtensionError } from '../../src/errors/expression-extension.error';
|
||||
import { extendTransform, extend } from '../../src/extensions';
|
||||
import { joinExpression, splitExpression } from '../../src/extensions/expression-parser';
|
||||
|
||||
describe('Expression Extension Transforms', () => {
|
||||
describe('extend() transform', () => {
|
||||
test('Basic transform with .isEmpty', () => {
|
||||
expect(extendTransform('"".isEmpty()')!.code).toEqual('extend("", "isEmpty", [])');
|
||||
});
|
||||
|
||||
test('Chained transform with .toSnakeCase.toSentenceCase', () => {
|
||||
expect(extendTransform('"".toSnakeCase().toSentenceCase(2)')!.code).toEqual(
|
||||
'extend(extend("", "toSnakeCase", []), "toSentenceCase", [2])',
|
||||
);
|
||||
});
|
||||
|
||||
test('Chained transform with native functions .toSnakeCase.trim.toSentenceCase', () => {
|
||||
expect(extendTransform('"aaa ".toSnakeCase().trim().toSentenceCase(2)')!.code).toEqual(
|
||||
'extend(extend("aaa ", "toSnakeCase", []).trim(), "toSentenceCase", [2])',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Expression Parser', () => {
|
||||
describe('Compatible splitting', () => {
|
||||
test('Lone expression', () => {
|
||||
expect(splitExpression('{{ "" }}')).toEqual([
|
||||
{ type: 'text', text: '' },
|
||||
{ type: 'code', text: ' "" ', hasClosingBrackets: true },
|
||||
]);
|
||||
});
|
||||
|
||||
test('Multiple expression', () => {
|
||||
expect(splitExpression('{{ "test".toSnakeCase() }} you have ${{ (100).format() }}.')).toEqual(
|
||||
[
|
||||
{ type: 'text', text: '' },
|
||||
{ type: 'code', text: ' "test".toSnakeCase() ', hasClosingBrackets: true },
|
||||
{ type: 'text', text: ' you have $' },
|
||||
{ type: 'code', text: ' (100).format() ', hasClosingBrackets: true },
|
||||
{ type: 'text', text: '.' },
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test('Unclosed expression', () => {
|
||||
expect(splitExpression('{{ "test".toSnakeCase() }} you have ${{ (100).format()')).toEqual([
|
||||
{ type: 'text', text: '' },
|
||||
{ type: 'code', text: ' "test".toSnakeCase() ', hasClosingBrackets: true },
|
||||
{ type: 'text', text: ' you have $' },
|
||||
{ type: 'code', text: ' (100).format()', hasClosingBrackets: false },
|
||||
]);
|
||||
});
|
||||
|
||||
test('Escaped opening bracket', () => {
|
||||
expect(splitExpression('test \\{{ no code }}')).toEqual([
|
||||
{ type: 'text', text: 'test \\{{ no code }}' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('Escaped closinging bracket', () => {
|
||||
expect(splitExpression('test {{ code.test("\\}}") }}')).toEqual([
|
||||
{ type: 'text', text: 'test ' },
|
||||
{ type: 'code', text: ' code.test("}}") ', hasClosingBrackets: true },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Compatible joining', () => {
|
||||
test('Lone expression', () => {
|
||||
expect(joinExpression(splitExpression('{{ "" }}'))).toEqual('{{ "" }}');
|
||||
});
|
||||
|
||||
test('Multiple expression', () => {
|
||||
expect(
|
||||
joinExpression(
|
||||
splitExpression('{{ "test".toSnakeCase() }} you have ${{ (100).format() }}.'),
|
||||
),
|
||||
).toEqual('{{ "test".toSnakeCase() }} you have ${{ (100).format() }}.');
|
||||
});
|
||||
|
||||
test('Unclosed expression', () => {
|
||||
expect(
|
||||
joinExpression(splitExpression('{{ "test".toSnakeCase() }} you have ${{ (100).format()')),
|
||||
).toEqual('{{ "test".toSnakeCase() }} you have ${{ (100).format()');
|
||||
});
|
||||
|
||||
test('Escaped opening bracket', () => {
|
||||
expect(joinExpression(splitExpression('test \\{{ no code }}'))).toEqual(
|
||||
'test \\{{ no code }}',
|
||||
);
|
||||
});
|
||||
|
||||
test('Escaped closing bracket', () => {
|
||||
expect(joinExpression(splitExpression('test {{ code.test("\\}}") }}'))).toEqual(
|
||||
'test {{ code.test("\\}}") }}',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge cases', () => {
|
||||
test("Nested member access with name of function inside a function doesn't result in function call", () => {
|
||||
expect(evaluate('={{ Math.floor([1, 2, 3, 4].length + 10) }}')).toEqual(14);
|
||||
|
||||
expect(extendTransform('Math.floor([1, 2, 3, 4].length + 10)')?.code).toBe(
|
||||
'extend(Math, "floor", [[1, 2, 3, 4].length + 10])',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test newer ES syntax', () => {
|
||||
test('Optional chaining transforms', () => {
|
||||
expect(extendTransform('$json.something?.test.funcCall()')?.code).toBe(
|
||||
'window.chainCancelToken1 = ((window.chainValue1 = $json.something) ?? undefined) === undefined, window.chainCancelToken1 === true ? undefined : window.chainValue1.test.funcCall();',
|
||||
);
|
||||
|
||||
expect(extendTransform('$json.something?.test.funcCall()?.somethingElse')?.code).toBe(
|
||||
'window.chainCancelToken1 = ((window.chainValue1 = $json.something) ?? undefined) === undefined, window.chainCancelToken1 === true ? undefined : window.chainCancelToken1 = ((window.chainValue1 = window.chainValue1.test.funcCall()) ?? undefined) === undefined, window.chainCancelToken1 === true ? undefined : window.chainValue1.somethingElse;',
|
||||
);
|
||||
|
||||
expect(extendTransform('$json.something?.test.funcCall().somethingElse')?.code).toBe(
|
||||
'window.chainCancelToken1 = ((window.chainValue1 = $json.something) ?? undefined) === undefined, window.chainCancelToken1 === true ? undefined : window.chainValue1.test.funcCall().somethingElse;',
|
||||
);
|
||||
|
||||
expect(
|
||||
extendTransform('$json.something?.test.funcCall()?.somethingElse.otherCall()')?.code,
|
||||
).toBe(
|
||||
'window.chainCancelToken1 = ((window.chainValue1 = $json.something) ?? undefined) === undefined, window.chainCancelToken1 === true ? undefined : window.chainCancelToken1 = ((window.chainValue1 = window.chainValue1.test.funcCall()) ?? undefined) === undefined, window.chainCancelToken1 === true ? undefined : window.chainValue1.somethingElse.otherCall();',
|
||||
);
|
||||
|
||||
expect(evaluate('={{ [1, 2, 3, 4]?.sum() }}')).toBe(10);
|
||||
});
|
||||
|
||||
test('Optional chaining transforms on calls', () => {
|
||||
expect(extendTransform('Math.min?.(1)')?.code).toBe(
|
||||
'window.chainCancelToken1 = ((window.chainValue1 = extendOptional(Math, "min")) ?? undefined) === undefined, window.chainCancelToken1 === true ? undefined : window.chainValue1(1);',
|
||||
);
|
||||
expect(extendTransform('Math?.min?.(1)')?.code).toBe(
|
||||
'window.chainCancelToken1 = ((window.chainValue1 = Math) ?? undefined) === undefined, window.chainCancelToken1 === true ? undefined : window.chainCancelToken1 = ((window.chainValue1 = extendOptional(window.chainValue1, "min")) ?? undefined) === undefined, window.chainCancelToken1 === true ? undefined : window.chainValue1(1);',
|
||||
);
|
||||
|
||||
expect(extendTransform('$json.test.test2?.sum()')?.code).toBe(
|
||||
'window.chainCancelToken1 = ((window.chainValue1 = $json.test.test2) ?? undefined) === undefined, window.chainCancelToken1 === true ? undefined : extend(window.chainValue1, "sum", []);',
|
||||
);
|
||||
expect(extendTransform('$json.test.test2?.sum?.()')?.code).toBe(
|
||||
'window.chainCancelToken1 = ((window.chainValue1 = $json.test.test2) ?? undefined) === undefined, window.chainCancelToken1 === true ? undefined : window.chainCancelToken1 = ((window.chainValue1 = extendOptional(window.chainValue1, "sum")) ?? undefined) === undefined, window.chainCancelToken1 === true ? undefined : window.chainValue1();',
|
||||
);
|
||||
|
||||
expect(evaluate('={{ [1, 2, 3, 4].sum?.() }}')).toBe(10);
|
||||
});
|
||||
|
||||
test('Multiple optional chains in an expression', () => {
|
||||
expect(extendTransform('$json.test?.test2($json.test?.test2)')?.code).toBe(`window.chainCancelToken2 = ((window.chainValue2 = $json.test) ?? undefined) === undefined, window.chainCancelToken2 === true ? undefined : window.chainValue2.test2(
|
||||
(window.chainCancelToken1 = ((window.chainValue1 = $json.test) ?? undefined) === undefined, window.chainCancelToken1 === true ? undefined : window.chainValue1.test2)
|
||||
);`);
|
||||
|
||||
expect(extendTransform('$json.test?.test2($json.test.sum?.())')?.code).toBe(`window.chainCancelToken2 = ((window.chainValue2 = $json.test) ?? undefined) === undefined, window.chainCancelToken2 === true ? undefined : window.chainValue2.test2(
|
||||
(window.chainCancelToken1 = ((window.chainValue1 = extendOptional($json.test, "sum")) ?? undefined) === undefined, window.chainCancelToken1 === true ? undefined : window.chainValue1())
|
||||
);`);
|
||||
});
|
||||
|
||||
expect(evaluate('={{ [1, 2, 3, 4]?.sum((undefined)?.test) }}')).toBe(10);
|
||||
});
|
||||
|
||||
describe('Non dot extensions', () => {
|
||||
test('min', () => {
|
||||
expect(evaluate('={{ min(1, 2, 3, 4, 5, 6) }}')).toEqual(1);
|
||||
expect(evaluate('={{ min(1, NaN, 3, 4, 5, 6) }}')).toBeNaN();
|
||||
});
|
||||
|
||||
test('max', () => {
|
||||
expect(evaluate('={{ max(1, 2, 3, 4, 5, 6) }}')).toEqual(6);
|
||||
expect(evaluate('={{ max(1, NaN, 3, 4, 5, 6) }}')).toBeNaN();
|
||||
});
|
||||
|
||||
test('average', () => {
|
||||
expect(evaluate('={{ average(1, 2, 3, 4, 5, 6) }}')).toEqual(3.5);
|
||||
expect(evaluate('={{ average(1, NaN, 3, 4, 5, 6) }}')).toBeNaN();
|
||||
});
|
||||
|
||||
test('numberList', () => {
|
||||
expect(evaluate('={{ numberList(1, 10) }}')).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
|
||||
expect(evaluate('={{ numberList(1, -10) }}')).toEqual([
|
||||
1, 0, -1, -2, -3, -4, -5, -6, -7, -8, -9, -10,
|
||||
]);
|
||||
});
|
||||
|
||||
test('zip', () => {
|
||||
expect(evaluate('={{ zip(["test1", "test2", "test3"], [1, 2, 3]) }}')).toEqual({
|
||||
test1: 1,
|
||||
test2: 2,
|
||||
test3: 3,
|
||||
});
|
||||
});
|
||||
|
||||
test('$if', () => {
|
||||
expect(evaluate('={{ $if("a"==="a", 1, 2) }}')).toEqual(1);
|
||||
expect(evaluate('={{ $if("a"==="b", 1, 2) }}')).toEqual(2);
|
||||
expect(evaluate('={{ $if("a"==="a", 1) }}')).toEqual(1);
|
||||
expect(evaluate('={{ $if("a"==="b", 1) }}')).toEqual(false);
|
||||
|
||||
// This will likely break when sandboxing is implemented but it works for now.
|
||||
// If you're implementing sandboxing maybe provide a way to add functions to
|
||||
// sandbox we can check instead?
|
||||
const mockCallback = vi.fn(() => false);
|
||||
evaluate('={{ $if("a"==="a", true, $data.cb()) }}', [{ cb: mockCallback }]);
|
||||
expect(mockCallback.mock.calls.length).toEqual(0);
|
||||
|
||||
evaluate('={{ $if("a"==="b", true, $data.cb()) }}', [{ cb: mockCallback }]);
|
||||
expect(mockCallback.mock.calls.length).toEqual(1);
|
||||
});
|
||||
|
||||
test('$not', () => {
|
||||
expect(evaluate('={{ $not(1) }}')).toEqual(false);
|
||||
expect(evaluate('={{ $not(0) }}')).toEqual(true);
|
||||
expect(evaluate('={{ $not(true) }}')).toEqual(false);
|
||||
expect(evaluate('={{ $not(false) }}')).toEqual(true);
|
||||
expect(evaluate('={{ $not(undefined) }}')).toEqual(true);
|
||||
expect(evaluate('={{ $not(null) }}')).toEqual(true);
|
||||
expect(evaluate('={{ $not("") }}')).toEqual(true);
|
||||
expect(evaluate('={{ $not("a") }}')).toEqual(false);
|
||||
});
|
||||
test('$ifEmpty', () => {
|
||||
expect(evaluate('={{ $ifEmpty(1, "default") }}')).toEqual(1);
|
||||
expect(evaluate('={{ $ifEmpty(0, "default") }}')).toEqual(0);
|
||||
expect(evaluate('={{ $ifEmpty(false, "default") }}')).toEqual(false);
|
||||
expect(evaluate('={{ $ifEmpty(true, "default") }}')).toEqual(true);
|
||||
expect(evaluate('={{ $ifEmpty("", "default") }}')).toEqual('default');
|
||||
expect(evaluate('={{ $ifEmpty(null, "default") }}')).toEqual('default');
|
||||
expect(evaluate('={{ $ifEmpty(undefined, "default") }}')).toEqual('default');
|
||||
expect(evaluate('={{ $ifEmpty([], "default") }}')).toEqual('default');
|
||||
expect(evaluate('={{ $ifEmpty({}, "default") }}')).toEqual('default');
|
||||
expect(evaluate('={{ $ifEmpty([1], "default") }}')).toEqual([1]);
|
||||
expect(evaluate('={{ $ifEmpty({a: 1}, "default") }}')).toEqual({ a: 1 });
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test extend with undefined', () => {
|
||||
test('input is undefined', () => {
|
||||
try {
|
||||
extend(undefined, 'toDateTime', []);
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(ExpressionExtensionError);
|
||||
expect(error).toHaveProperty('message', "toDateTime can't be used on undefined value");
|
||||
}
|
||||
});
|
||||
test('input is null', () => {
|
||||
try {
|
||||
extend(null, 'startsWith', []);
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(ExpressionExtensionError);
|
||||
expect(error).toHaveProperty('message', "startsWith can't be used on null value");
|
||||
}
|
||||
});
|
||||
test('input should be converted to upper case', () => {
|
||||
const result = extend('TEST', 'toUpperCase', []);
|
||||
|
||||
expect(result).toEqual('TEST');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
import { evaluate } from './helpers';
|
||||
|
||||
describe('Data Transformation Functions', () => {
|
||||
describe('Genric Data Transformation Functions', () => {
|
||||
test('.isEmpty() should work correctly on undefined', () => {
|
||||
expect(evaluate('={{(undefined).isEmpty()}}')).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
import type { IDataObject } from '../../src/interfaces';
|
||||
import { Workflow } from '../../src/workflow';
|
||||
import * as Helpers from '../helpers';
|
||||
|
||||
export const nodeTypes = Helpers.NodeTypes();
|
||||
export const workflow = new Workflow({
|
||||
nodes: [
|
||||
{
|
||||
name: 'node',
|
||||
typeVersion: 1,
|
||||
type: 'test.set',
|
||||
id: 'uuid-1234',
|
||||
position: [0, 0],
|
||||
parameters: {},
|
||||
},
|
||||
],
|
||||
connections: {},
|
||||
active: false,
|
||||
nodeTypes,
|
||||
});
|
||||
export const expression = workflow.expression;
|
||||
|
||||
export const evaluate = (value: string, values?: IDataObject[]) =>
|
||||
expression.getParameterValue(
|
||||
value,
|
||||
null,
|
||||
0,
|
||||
0,
|
||||
'node',
|
||||
values?.map((v) => ({ json: v })) ?? [],
|
||||
'manual',
|
||||
{},
|
||||
);
|
||||
|
||||
export const getLocalISOString = (date: Date) => {
|
||||
const offset = date.getTimezoneOffset();
|
||||
const offsetAbs = Math.abs(offset);
|
||||
const isoString = new Date(date.getTime() - offset * 60 * 1000).toISOString();
|
||||
const hours = String(Math.floor(offsetAbs / 60)).padStart(2, '0');
|
||||
const minutes = String(offsetAbs % 60).padStart(2, '0');
|
||||
return `${isoString.slice(0, -1)}${offset > 0 ? '-' : '+'}${hours}:${minutes}`;
|
||||
};
|
||||
@@ -0,0 +1,118 @@
|
||||
// @vitest-environment jsdom
|
||||
|
||||
import { evaluate } from './helpers';
|
||||
import { numberExtensions } from '../../src/extensions/number-extensions';
|
||||
|
||||
describe('Data Transformation Functions', () => {
|
||||
describe('Number Data Transformation Functions', () => {
|
||||
test('.format() should work correctly on a number', () => {
|
||||
expect(evaluate('={{ Number(100).format() }}')).toEqual(
|
||||
numberExtensions.functions.format(100, []),
|
||||
);
|
||||
});
|
||||
|
||||
test('.ceil() should work on a number', () => {
|
||||
expect(evaluate('={{ (1.2).ceil() }}')).toEqual(2);
|
||||
expect(evaluate('={{ (1.9).ceil() }}')).toEqual(2);
|
||||
expect(evaluate('={{ (1.0).ceil() }}')).toEqual(1);
|
||||
expect(evaluate('={{ (NaN).ceil() }}')).toBeNaN();
|
||||
});
|
||||
|
||||
test('.floor() should work on a number', () => {
|
||||
expect(evaluate('={{ (1.2).floor() }}')).toEqual(1);
|
||||
expect(evaluate('={{ (1.9).floor() }}')).toEqual(1);
|
||||
expect(evaluate('={{ (1.0).floor() }}')).toEqual(1);
|
||||
expect(evaluate('={{ (NaN).floor() }}')).toBeNaN();
|
||||
});
|
||||
|
||||
test('.round() should work on a number', () => {
|
||||
expect(evaluate('={{ (1.3333333).round(3) }}')).toEqual(1.333);
|
||||
expect(evaluate('={{ (1.3333333).round(0) }}')).toEqual(1);
|
||||
expect(evaluate('={{ (1.5001).round(0) }}')).toEqual(2);
|
||||
expect(evaluate('={{ (NaN).round(3) }}')).toBeNaN();
|
||||
});
|
||||
|
||||
test('.isOdd() should work on a number', () => {
|
||||
expect(evaluate('={{ (9).isOdd() }}')).toEqual(true);
|
||||
expect(evaluate('={{ (8).isOdd() }}')).toEqual(false);
|
||||
expect(evaluate('={{ (0).isOdd() }}')).toEqual(false);
|
||||
});
|
||||
|
||||
test('.isOdd() should not work on a float or NaN', () => {
|
||||
expect(() => evaluate('={{ (NaN).isOdd() }}')).toThrow();
|
||||
expect(() => evaluate('={{ (9.2).isOdd() }}')).toThrow();
|
||||
});
|
||||
|
||||
test('.isEven() should work on a number', () => {
|
||||
expect(evaluate('={{ (9).isEven() }}')).toEqual(false);
|
||||
expect(evaluate('={{ (8).isEven() }}')).toEqual(true);
|
||||
expect(evaluate('={{ (0).isEven() }}')).toEqual(true);
|
||||
});
|
||||
|
||||
test('.isEven() should not work on a float or NaN', () => {
|
||||
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',
|
||||
);
|
||||
});
|
||||
|
||||
test('from microseconds', () => {
|
||||
expect(evaluate('={{ (1704085200000000).toDateTime("us").toISO() }}')).toEqual(
|
||||
'2024-01-01T00:00:00.000-05: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', () => {
|
||||
test('Basic multiple expressions', () => {
|
||||
// eslint-disable-next-line n8n-local-rules/no-interpolation-in-regular-string
|
||||
expect(evaluate('={{ "test abc".toSnakeCase() }} you have ${{ (100).format() }}.')).toEqual(
|
||||
'test_abc you have $100.',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,184 @@
|
||||
import { evaluate } from './helpers';
|
||||
import { ApplicationError } from '../../src/errors';
|
||||
import { objectExtensions } from '../../src/extensions/object-extensions';
|
||||
|
||||
describe('Data Transformation Functions', () => {
|
||||
describe('Object Data Transformation Functions', () => {
|
||||
describe('.isEmpty', () => {
|
||||
test('should return true for an empty object', () => {
|
||||
expect(evaluate('={{ ({}).isEmpty() }}')).toBe(true);
|
||||
});
|
||||
|
||||
test('should return false for a non-empty object', () => {
|
||||
expect(evaluate('={{ ({ test: 1 }).isEmpty() }}')).toBe(false);
|
||||
});
|
||||
|
||||
test('should return true for an object with only null/undefined values', () => {
|
||||
expect(evaluate('={{ ({ test1: null, test2: undefined }).isEmpty() }}')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('.hasField', () => {
|
||||
test('should return true if the key exists in the object', () => {
|
||||
expect(evaluate('={{ ({ test1: 1 }).hasField("test1") }}')).toBe(true);
|
||||
});
|
||||
|
||||
test('should return false if the key does not exist in the object', () => {
|
||||
expect(evaluate('={{ ({ test1: 1 }).hasField("test2") }}')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
test('.removeField should work on an object', () => {
|
||||
expect(evaluate('={{ ({ test1: 1, test2: 2, test3: 3 }).removeField("test2") }}')).toEqual({
|
||||
test1: 1,
|
||||
test3: 3,
|
||||
});
|
||||
expect(
|
||||
evaluate('={{ ({ test1: 1, test2: 2, test3: 3 }).removeField("testDoesntExist") }}'),
|
||||
).toEqual({
|
||||
test1: 1,
|
||||
test2: 2,
|
||||
test3: 3,
|
||||
});
|
||||
});
|
||||
|
||||
describe('.removeFieldsContaining', () => {
|
||||
test('should work on an object', () => {
|
||||
expect(
|
||||
evaluate(
|
||||
'={{ ({ test1: "i exist", test2: "i should be removed", test3: "i should also be removed" }).removeFieldsContaining("removed") }}',
|
||||
),
|
||||
).toEqual({
|
||||
test1: 'i exist',
|
||||
});
|
||||
});
|
||||
|
||||
test('should not work for empty string', () => {
|
||||
expect(() =>
|
||||
evaluate(
|
||||
'={{ ({ test1: "i exist", test2: "i should be removed", test3: "i should also be removed" }).removeFieldsContaining("") }}',
|
||||
),
|
||||
).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('.keepFieldsContaining', () => {
|
||||
test('.keepFieldsContaining should work on an object', () => {
|
||||
expect(
|
||||
evaluate(
|
||||
'={{ ({ test1: "i exist", test2: "i should be removed", test3: "i should also be removed" }).keepFieldsContaining("exist") }}',
|
||||
),
|
||||
).toEqual({
|
||||
test1: 'i exist',
|
||||
});
|
||||
});
|
||||
|
||||
test('.keepFieldsContaining should work on a nested object', () => {
|
||||
expect(
|
||||
evaluate(
|
||||
'={{ ({ test1: "i exist", test2: "i should be removed", test3: { test4: "me too" } }).keepFieldsContaining("exist") }}',
|
||||
),
|
||||
).toEqual({
|
||||
test1: 'i exist',
|
||||
});
|
||||
});
|
||||
|
||||
test('.keepFieldsContaining should not work for empty string', () => {
|
||||
expect(() =>
|
||||
evaluate(
|
||||
'={{ ({ test1: "i exist", test2: "i should be removed", test3: "i should also be removed" }).keepFieldsContaining("") }}',
|
||||
),
|
||||
).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('.compact', () => {
|
||||
test('should work on an object', () => {
|
||||
expect(
|
||||
evaluate('={{ ({ test1: 1, test2: "2", test3: undefined, test4: null }).compact() }}'),
|
||||
).toEqual({ test1: 1, test2: '2' });
|
||||
});
|
||||
|
||||
test('should remove fields with null, undefined, empty string, or "nil"', () => {
|
||||
expect(
|
||||
evaluate(
|
||||
'={{ ({ test1: 0, test2: false, test3: "", test4: "nil", test5: NaN }).compact() }}',
|
||||
),
|
||||
).toEqual({ test1: 0, test2: false, test5: NaN });
|
||||
});
|
||||
|
||||
test('should work on an empty object', () => {
|
||||
expect(evaluate('={{ ({}).compact() }}')).toEqual({});
|
||||
});
|
||||
|
||||
test('should work on an object with all null/undefined values', () => {
|
||||
expect(evaluate('={{ ({ test1: undefined, test2: null }).compact() }}')).toEqual({});
|
||||
});
|
||||
|
||||
test('should work on an object with nested null/undefined values', () => {
|
||||
expect(
|
||||
evaluate(
|
||||
'={{ ({ test1: 1, test2: { nested1: null, nested2: "value" }, test3: undefined }).compact() }}',
|
||||
),
|
||||
).toEqual({ test1: 1, test2: { nested2: 'value' } });
|
||||
});
|
||||
|
||||
test('should not allow prototype pollution', () => {
|
||||
['{__proto__: {polluted: true}}', '{constructor: {prototype: {polluted: true}}}'].forEach(
|
||||
(testExpression) => {
|
||||
expect(() => evaluate(`={{ (${testExpression}).compact() }}`)).toThrow(
|
||||
ApplicationError,
|
||||
);
|
||||
expect(({} as any).polluted).toBeUndefined();
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('.urlEncode should work on an object', () => {
|
||||
expect(evaluate('={{ ({ test1: 1, test2: "2" }).urlEncode() }}')).toEqual('test1=1&test2=2');
|
||||
});
|
||||
|
||||
describe('.keys', () => {
|
||||
test('should return an array of keys from the object', () => {
|
||||
expect(evaluate('={{ ({ test1: 1, test2: 2 }).keys() }}')).toEqual(['test1', 'test2']);
|
||||
});
|
||||
|
||||
test('should return an empty array for an empty object', () => {
|
||||
expect(evaluate('={{ ({}).keys() }}')).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('.values', () => {
|
||||
test('should return an array of values from the object', () => {
|
||||
expect(evaluate('={{ ({ test1: 1, test2: "value" }).values() }}')).toEqual([1, 'value']);
|
||||
});
|
||||
|
||||
test('should return an empty array for an empty object', () => {
|
||||
expect(evaluate('={{ ({}).values() }}')).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,340 @@
|
||||
// @vitest-environment jsdom
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { evaluate } from './helpers';
|
||||
import { ExpressionExtensionError } from '../../src/errors';
|
||||
|
||||
describe('Data Transformation Functions', () => {
|
||||
describe('String Data Transformation Functions', () => {
|
||||
describe('.isEmpty', () => {
|
||||
test('should work correctly on a string that is not empty', () => {
|
||||
expect(evaluate('={{"NotBlank".isEmpty()}}')).toEqual(false);
|
||||
});
|
||||
|
||||
test('should work correctly on a string that is empty', () => {
|
||||
expect(evaluate('={{"".isEmpty()}}')).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('.isNotEmpty', () => {
|
||||
test('should work correctly on a string that is not empty', () => {
|
||||
expect(evaluate('={{"NotBlank".isNotEmpty()}}')).toEqual(true);
|
||||
});
|
||||
|
||||
test('should work correctly on a string that is empty', () => {
|
||||
expect(evaluate('={{"".isNotEmpty()}}')).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
test('.length should return the string length', () => {
|
||||
expect(evaluate('={{"String".length()}}')).toEqual(6);
|
||||
});
|
||||
|
||||
describe('.hash()', () => {
|
||||
test.each([
|
||||
['base64', 'MTIzNDU='],
|
||||
['md5', '827ccb0eea8a706c4c34a16891f84e7b'],
|
||||
['sha1', '8cb2237d0679ca88db6464eac60da96345513964'],
|
||||
['sha224', 'a7470858e79c282bc2f6adfd831b132672dfd1224c1e78cbf5bcd057'],
|
||||
['sha256', '5994471abb01112afcc18159f6cc74b4f511b99806da59b3caf5a9c173cacfc5'],
|
||||
[
|
||||
'sha384',
|
||||
'0fa76955abfa9dafd83facca8343a92aa09497f98101086611b0bfa95dbc0dcc661d62e9568a5a032ba81960f3e55d4a',
|
||||
],
|
||||
[
|
||||
'sha512',
|
||||
'3627909a29c31381a071ec27f7c9ca97726182aed29a7ddd2e54353322cfb30abb9e3a6df2ac2c20fe23436311d678564d0c8d305930575f60e2d3d048184d79',
|
||||
],
|
||||
[
|
||||
'sha3',
|
||||
'0a2a1719bf3ce682afdbedf3b23857818d526efbe7fcb372b31347c26239a0f916c398b7ad8dd0ee76e8e388604d0b0f925d5e913ad2d3165b9b35b3844cd5e6',
|
||||
],
|
||||
])('should work for %p', (hashFn, hashValue) => {
|
||||
expect(evaluate(`={{ "12345".hash("${hashFn}") }}`)).toEqual(hashValue);
|
||||
expect(evaluate(`={{ "12345".hash("${hashFn.toLowerCase()}") }}`)).toEqual(hashValue);
|
||||
});
|
||||
|
||||
test('should throw on invalid algorithm', () => {
|
||||
expect(() => evaluate('={{ "12345".hash("invalid") }}')).toThrow('Unknown algorithm');
|
||||
});
|
||||
});
|
||||
|
||||
test('.urlDecode should work correctly on a string', () => {
|
||||
expect(evaluate('={{ "string%20with%20spaces".urlDecode(false) }}')).toEqual(
|
||||
'string with spaces',
|
||||
);
|
||||
});
|
||||
|
||||
test('.urlEncode should work correctly on a string', () => {
|
||||
expect(evaluate('={{ "string with spaces".urlEncode(false) }}')).toEqual(
|
||||
'string%20with%20spaces',
|
||||
);
|
||||
});
|
||||
|
||||
test('.removeTags should work correctly on a string', () => {
|
||||
expect(evaluate('={{ "<html><head>test</head></html>".removeTags() }}')).toEqual('test');
|
||||
});
|
||||
|
||||
test('.removeMarkdown should work correctly on a string', () => {
|
||||
expect(evaluate('={{ "<html><head>test</head></html>".removeMarkdown() }}')).toEqual('test');
|
||||
});
|
||||
|
||||
test('.toLowerCase should work correctly on a string', () => {
|
||||
expect(evaluate('={{ "TEST".toLowerCase() }}')).toEqual('test');
|
||||
});
|
||||
|
||||
describe('.toDate', () => {
|
||||
test('should work correctly on a date string', () => {
|
||||
expect(evaluate('={{ "2022-09-01T19:42:28.164Z".toDate() }}')).toEqual(
|
||||
new Date('2022-09-01T19:42:28.164Z'),
|
||||
);
|
||||
});
|
||||
|
||||
test('should throw on invalid date', () => {
|
||||
expect(() => evaluate('={{ "2022-09-32T19:42:28.164Z".toDate() }}')).toThrow(
|
||||
'cannot convert to date',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('.toFloat should work correctly on a string', () => {
|
||||
expect(evaluate('={{ "1.1".toFloat() }}')).toEqual(1.1);
|
||||
expect(evaluate('={{ "1.1".toDecimalNumber() }}')).toEqual(1.1);
|
||||
});
|
||||
|
||||
test('.toInt should work correctly on a string', () => {
|
||||
expect(evaluate('={{ "1.1".toInt() }}')).toEqual(1);
|
||||
expect(evaluate('={{ "1.1".toWholeNumber() }}')).toEqual(1);
|
||||
expect(evaluate('={{ "1.5".toInt() }}')).toEqual(1);
|
||||
expect(evaluate('={{ "1.5".toWholeNumber() }}')).toEqual(1);
|
||||
});
|
||||
|
||||
test('.quote should work correctly on a string', () => {
|
||||
expect(evaluate('={{ "test".quote() }}')).toEqual('"test"');
|
||||
expect(evaluate('={{ "\\"test\\"".quote() }}')).toEqual('"\\"test\\""');
|
||||
});
|
||||
|
||||
test('.isNumeric should work correctly on a string', () => {
|
||||
expect(evaluate('={{ "".isNumeric() }}')).toEqual(false);
|
||||
expect(evaluate('={{ "asdf".isNumeric() }}')).toEqual(false);
|
||||
expect(evaluate('={{ "1234".isNumeric() }}')).toEqual(true);
|
||||
expect(evaluate('={{ "4e4".isNumeric() }}')).toEqual(true);
|
||||
expect(evaluate('={{ "4.4".isNumeric() }}')).toEqual(true);
|
||||
});
|
||||
|
||||
test('.isUrl should work on a string', () => {
|
||||
expect(evaluate('={{ "https://example.com/".isUrl() }}')).toEqual(true);
|
||||
expect(evaluate('={{ "http://example.com/".isUrl() }}')).toEqual(true);
|
||||
expect(evaluate('={{ "ftp://example.com/".isUrl() }}')).toEqual(true);
|
||||
expect(evaluate('={{ "example.com".isUrl() }}')).toEqual(false);
|
||||
expect(evaluate('={{ "www.example.com".isUrl() }}')).toEqual(false);
|
||||
expect(evaluate('={{ "https://www.example.com/".isUrl() }}')).toEqual(true);
|
||||
expect(evaluate('={{ "https://example.com/path".isUrl() }}')).toEqual(true);
|
||||
expect(evaluate('={{ "https://example.com/path?query=1".isUrl() }}')).toEqual(true);
|
||||
expect(evaluate('={{ "https://example.com/path#fragment".isUrl() }}')).toEqual(true);
|
||||
expect(evaluate('={{ "https://example.com:8080".isUrl() }}')).toEqual(true);
|
||||
expect(evaluate('={{ "https://example.com?query=1".isUrl() }}')).toEqual(true);
|
||||
expect(evaluate('={{ "https://example.com#fragment".isUrl() }}')).toEqual(true);
|
||||
expect(evaluate('={{ "example.com/path".isUrl() }}')).toEqual(false);
|
||||
expect(evaluate('={{ "http:///".isUrl() }}')).toEqual(false);
|
||||
expect(evaluate('={{ "https://".isUrl() }}')).toEqual(false);
|
||||
expect(evaluate('={{ "example".isUrl() }}')).toEqual(false);
|
||||
expect(evaluate('={{ "".isUrl() }}')).toEqual(false);
|
||||
});
|
||||
|
||||
test('.isDomain should work on a string', () => {
|
||||
expect(evaluate('={{ "example.com".isDomain() }}')).toEqual(true);
|
||||
expect(evaluate('={{ "asdf".isDomain() }}')).toEqual(false);
|
||||
expect(evaluate('={{ "https://example.com/".isDomain() }}')).toEqual(false);
|
||||
expect(evaluate('={{ "www.example.com".isDomain() }}')).toEqual(true);
|
||||
expect(evaluate('={{ "subdomain.example.com".isDomain() }}')).toEqual(true);
|
||||
expect(evaluate('={{ "example.co.uk".isDomain() }}')).toEqual(true);
|
||||
expect(evaluate('={{ "example".isDomain() }}')).toEqual(false);
|
||||
expect(evaluate('={{ "example.".isDomain() }}')).toEqual(false);
|
||||
expect(evaluate('={{ ".com".isDomain() }}')).toEqual(false);
|
||||
expect(evaluate('={{ "example..com".isDomain() }}')).toEqual(false);
|
||||
expect(evaluate('={{ "example_com".isDomain() }}')).toEqual(false);
|
||||
expect(evaluate('={{ "example/com".isDomain() }}')).toEqual(false);
|
||||
expect(evaluate('={{ "example com".isDomain() }}')).toEqual(false);
|
||||
expect(evaluate('={{ "www.example..com".isDomain() }}')).toEqual(false);
|
||||
expect(evaluate('={{ "123.com".isDomain() }}')).toEqual(true);
|
||||
expect(evaluate('={{ "xn--80aswg.xn--p1ai".isDomain() }}')).toEqual(true); // Punycode domain
|
||||
expect(evaluate('={{ "example.com:8080".isDomain() }}')).toEqual(true);
|
||||
expect(evaluate('={{ "".isDomain() }}')).toEqual(false);
|
||||
});
|
||||
|
||||
test('.toSnakeCase should work on a string', () => {
|
||||
expect(evaluate('={{ "I am a test!".toSnakeCase() }}')).toEqual('i_am_a_test');
|
||||
expect(evaluate('={{ "i_am_a_test".toSnakeCase() }}')).toEqual('i_am_a_test');
|
||||
});
|
||||
|
||||
test('.toSentenceCase should work on a string', () => {
|
||||
expect(
|
||||
evaluate(
|
||||
'={{ "i am a test! i have multiple types of Punctuation. or do i?".toSentenceCase() }}',
|
||||
),
|
||||
).toEqual('I am a test! I have multiple types of punctuation. Or do i?');
|
||||
expect(evaluate('={{ "i am a test!".toSentenceCase() }}')).toEqual('I am a test!');
|
||||
expect(evaluate('={{ "i am a test".toSentenceCase() }}')).toEqual('I am a test');
|
||||
});
|
||||
|
||||
test('.extractUrl should work on a string', () => {
|
||||
expect(
|
||||
evaluate(
|
||||
'={{ "I am a test with a url: https://example.net/ and I am a test with an email: test@example.org".extractUrl() }}',
|
||||
),
|
||||
).toEqual('https://example.net/');
|
||||
expect(
|
||||
evaluate(
|
||||
'={{ "Check this out: https://subdomain.example.com:3000/path?q=1#hash".extractUrl() }}',
|
||||
),
|
||||
).toEqual('https://subdomain.example.com:3000/path?q=1#hash');
|
||||
expect(evaluate('={{ "Invalid URL: http:///example.com".extractUrl() }}')).toEqual(undefined);
|
||||
expect(
|
||||
evaluate(
|
||||
'={{ "Mixed content: https://www.example.com and http://www.example.org".extractUrl() }}',
|
||||
),
|
||||
).toEqual('https://www.example.com');
|
||||
expect(
|
||||
evaluate('={{ "Text without URL: This is just a simple text".extractUrl() }}'),
|
||||
).toEqual(undefined);
|
||||
expect(
|
||||
evaluate('={{ "URL with Unicode: http://www.xn--80aswg.xn--j1amh".extractUrl() }}'),
|
||||
).toEqual('http://www.xn--80aswg.xn--j1amh');
|
||||
expect(
|
||||
evaluate('={{ "Localhost URL: http://localhost:8080/test?x=1".extractUrl() }}'),
|
||||
).toEqual('http://localhost:8080/test?x=1');
|
||||
expect(
|
||||
evaluate('={{ "IP URL: http://192.168.1.1:8000/path?q=value#frag".extractUrl() }}'),
|
||||
).toEqual('http://192.168.1.1:8000/path?q=value#frag');
|
||||
});
|
||||
|
||||
test('.extractDomain should work on a string', () => {
|
||||
expect(evaluate('={{ "test@example.org".extractDomain() }}')).toEqual('example.org');
|
||||
expect(evaluate('={{ "https://example.org/".extractDomain() }}')).toEqual('example.org');
|
||||
expect(evaluate('={{ "https://www.google.com".extractDomain() }}')).toEqual('www.google.com');
|
||||
expect(evaluate('={{ "http://example.org".extractDomain() }}')).toEqual('example.org');
|
||||
expect(evaluate('={{ "ftp://ftp.example.com".extractDomain() }}')).toEqual('ftp.example.com');
|
||||
expect(evaluate('={{ "google.com".extractDomain() }}')).toEqual('google.com');
|
||||
expect(evaluate('={{ "www.example.net".extractDomain() }}')).toEqual('www.example.net');
|
||||
expect(evaluate('={{ "//example.com".extractDomain() }}')).toEqual('example.com');
|
||||
expect(evaluate('={{ "mailto:john.doe@example.com".extractDomain() }}')).toEqual(
|
||||
'example.com',
|
||||
);
|
||||
expect(evaluate('={{ "tel:+1-555-123-4567".extractDomain() }}')).toEqual(undefined);
|
||||
expect(evaluate('={{ "jane.doe@example.org".extractDomain() }}')).toEqual('example.org');
|
||||
expect(evaluate('={{ "name+tag@example.com".extractDomain() }}')).toEqual('example.com');
|
||||
expect(evaluate('={{ "first.last@example.co.uk".extractDomain() }}')).toEqual(
|
||||
'example.co.uk',
|
||||
);
|
||||
expect(evaluate('={{ "user@subdomain.example.com".extractDomain() }}')).toEqual(
|
||||
'subdomain.example.com',
|
||||
);
|
||||
expect(evaluate('={{ "www.example.net?test=1213".extractDomain() }}')).toEqual(
|
||||
'www.example.net',
|
||||
);
|
||||
expect(evaluate('={{ "www.example.net?test".extractDomain() }}')).toEqual('www.example.net');
|
||||
expect(evaluate('={{ "www.example.net#tesdt123".extractDomain() }}')).toEqual(
|
||||
'www.example.net',
|
||||
);
|
||||
expect(evaluate('={{ "https://www.example.net?test=1213".extractDomain() }}')).toEqual(
|
||||
'www.example.net',
|
||||
);
|
||||
expect(evaluate('={{ "https://www.example.net?test".extractDomain() }}')).toEqual(
|
||||
'www.example.net',
|
||||
);
|
||||
expect(evaluate('={{ "https://www.example.net#tesdt123".extractDomain() }}')).toEqual(
|
||||
'www.example.net',
|
||||
);
|
||||
expect(evaluate('={{ "https://192.168.1.1".extractDomain() }}')).toEqual('192.168.1.1');
|
||||
expect(evaluate('={{ "http://www.xn--80aswg.xn--j1amh".extractDomain() }}')).toEqual(
|
||||
'www.xn--80aswg.xn--j1amh',
|
||||
);
|
||||
expect(evaluate('={{ "https://localhost".extractDomain() }}')).toEqual('localhost');
|
||||
expect(evaluate('={{ "https://localhost?test=123".extractDomain() }}')).toEqual('localhost');
|
||||
expect(evaluate('={{ "https://www.example_with_underscore.com".extractDomain() }}')).toEqual(
|
||||
'www.example_with_underscore.com',
|
||||
);
|
||||
expect(evaluate('={{ "https://www.example.com:8080".extractDomain() }}')).toEqual(
|
||||
'www.example.com',
|
||||
);
|
||||
expect(evaluate('={{ "https://example.space".extractDomain() }}')).toEqual('example.space');
|
||||
});
|
||||
|
||||
test('.extractEmail should work on a string', () => {
|
||||
expect(
|
||||
evaluate(
|
||||
'={{ "I am a test with a url: https://example.net/ and I am a test with an email: test@example.org".extractEmail() }}',
|
||||
),
|
||||
).toEqual('test@example.org');
|
||||
});
|
||||
|
||||
test('.isEmail should work on a string', () => {
|
||||
expect(evaluate('={{ "test@example.com".isEmail() }}')).toEqual(true);
|
||||
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('={{ "1713976144063".toDateTime("ms") }}')).toBeInstanceOf(DateTime);
|
||||
expect(evaluate('={{ "31-01-2024".toDateTime("dd-MM-yyyy") }}')).toBeInstanceOf(DateTime);
|
||||
|
||||
vi.useFakeTimers({ now: new Date() });
|
||||
expect(() => evaluate('={{ "hi".toDateTime() }}')).toThrow(
|
||||
new ExpressionExtensionError('cannot convert to Luxon DateTime'),
|
||||
);
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
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',
|
||||
});
|
||||
});
|
||||
|
||||
test('.parseJson should throw on invalid JSON', () => {
|
||||
expect(() => evaluate("={{ \"{'test1':1,'test2':'2'}\".parseJson() }}")).toThrowError(
|
||||
"Parsing failed. Check you're using double quotes",
|
||||
);
|
||||
expect(() => evaluate('={{ "No JSON here".parseJson() }}')).toThrowError('Parsing failed');
|
||||
});
|
||||
|
||||
test('.toJsonString should work on a string', () => {
|
||||
expect(evaluate('={{ "test".toJsonString() }}')).toEqual(JSON.stringify('test'));
|
||||
expect(evaluate('={{ "The \\"best\\" colours: red\\nbrown".toJsonString() }}')).toEqual(
|
||||
JSON.stringify('The "best" colours: red\nbrown'),
|
||||
);
|
||||
expect(evaluate('={{ "".toJsonString() }}')).toEqual(JSON.stringify(''));
|
||||
});
|
||||
|
||||
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