ci: Start typechecking n8n-workflow and n8n-core (no-changelog) (#9925)

Co-authored-by: Danny Martini <danny@n8n.io>
This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™ 2024-07-03 18:27:07 +02:00 committed by GitHub
parent 47cd411436
commit 24091dfd9b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 479 additions and 410 deletions

View file

@ -13,7 +13,7 @@
"build": "turbo run build", "build": "turbo run build",
"build:backend": "pnpm --filter=!@n8n/chat --filter=!@n8n/codemirror-lang --filter=!n8n-design-system --filter=!n8n-editor-ui build", "build:backend": "pnpm --filter=!@n8n/chat --filter=!@n8n/codemirror-lang --filter=!n8n-design-system --filter=!n8n-editor-ui build",
"build:frontend": "pnpm --filter=@n8n/chat --filter=@n8n/codemirror-lang --filter=n8n-design-system --filter=n8n-editor-ui build", "build:frontend": "pnpm --filter=@n8n/chat --filter=@n8n/codemirror-lang --filter=n8n-design-system --filter=n8n-editor-ui build",
"typecheck": "pnpm --filter=!@n8n/storybook --filter=!n8n-core --filter=!n8n-workflow --filter=!n8n typecheck", "typecheck": "pnpm --filter=!n8n typecheck",
"dev": "turbo run dev --parallel --filter=!n8n-design-system --filter=!@n8n/chat", "dev": "turbo run dev --parallel --filter=!n8n-design-system --filter=!@n8n/chat",
"dev:ai": "turbo run dev --parallel --filter=@n8n/nodes-langchain --filter=n8n --filter=n8n-core", "dev:ai": "turbo run dev --parallel --filter=@n8n/nodes-langchain --filter=n8n --filter=n8n-core",
"clean": "turbo run clean --parallel", "clean": "turbo run clean --parallel",

File diff suppressed because it is too large Load diff

View file

@ -23,7 +23,10 @@ const getTargetType = (type: FilterOperatorType) => {
return 'string'; return 'string';
}; };
const convertToType = (value: NodeParameterValue, type: FilterOperatorType): NodeParameterValue => { const convertToType = (
value: NodeParameterValue | NodeParameterValue[],
type: FilterOperatorType,
): NodeParameterValue | NodeParameterValue[] => {
if (type === 'any') return value; if (type === 'any') return value;
const fallback = type === 'boolean' ? false : value; const fallback = type === 'boolean' ? false : value;

View file

@ -2474,9 +2474,9 @@ export interface FilterOperatorValue {
export type FilterConditionValue = { export type FilterConditionValue = {
id: string; id: string;
leftValue: NodeParameterValue; leftValue: NodeParameterValue | NodeParameterValue[];
operator: FilterOperatorValue; operator: FilterOperatorValue;
rightValue: NodeParameterValue; rightValue: NodeParameterValue | NodeParameterValue[];
}; };
export type FilterOptionsValue = { export type FilterOptionsValue = {

View file

@ -7,6 +7,12 @@ type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P]; [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
}; };
type Tests = Array<{
left: FilterConditionValue['leftValue'];
right: FilterConditionValue['rightValue'];
expected: boolean;
}>;
const filterFactory = (data: DeepPartial<FilterValue> = {}): FilterValue => const filterFactory = (data: DeepPartial<FilterValue> = {}): FilterValue =>
merge( merge(
{ {
@ -832,21 +838,24 @@ describe('FilterParameter', () => {
right: '1-Feb-2024', right: '1-Feb-2024',
expected: false, expected: false,
}, },
])('dateTime:after("$left", "$right") === $expected', ({ left, right, expected }) => { ] as Tests)(
const result = executeFilter( 'dateTime:after("$left", "$right") === $expected',
filterFactory({ ({ left, right, expected }) => {
conditions: [ const result = executeFilter(
{ filterFactory({
id: '1', conditions: [
leftValue: left, {
rightValue: right, id: '1',
operator: { operation: 'after', type: 'dateTime' }, leftValue: left,
}, rightValue: right,
], operator: { operation: 'after', type: 'dateTime' },
}), },
); ],
expect(result).toBe(expected); }),
}); );
expect(result).toBe(expected);
},
);
it.each([ it.each([
{ left: '2023-11-15T17:10:49.113Z', right: '2023-11-15T17:10:49.113Z', expected: false }, { left: '2023-11-15T17:10:49.113Z', right: '2023-11-15T17:10:49.113Z', expected: false },
@ -1035,7 +1044,7 @@ describe('FilterParameter', () => {
it.each([ it.each([
{ left: ['foo', 'bar'], right: 'foo', expected: true }, { left: ['foo', 'bar'], right: 'foo', expected: true },
{ left: ['foo', 'bar'], right: 'ba', expected: false }, { left: ['foo', 'bar'], right: 'ba', expected: false },
])('array:contains($left,$right) === $expected', ({ left, right, expected }) => { ] as Tests)('array:contains($left,$right) === $expected', ({ left, right, expected }) => {
const result = executeFilter( const result = executeFilter(
filterFactory({ filterFactory({
conditions: [ conditions: [
@ -1054,67 +1063,76 @@ describe('FilterParameter', () => {
it.each([ it.each([
{ left: ['foo', 'bar'], right: 'foo', expected: false }, { left: ['foo', 'bar'], right: 'foo', expected: false },
{ left: ['foo', 'bar'], right: 'ba', expected: true }, { left: ['foo', 'bar'], right: 'ba', expected: true },
])('array:notContains($left,$right) === $expected', ({ left, right, expected }) => { ] as Tests)(
const result = executeFilter( 'array:notContains($left,$right) === $expected',
filterFactory({ ({ left, right, expected }) => {
conditions: [ const result = executeFilter(
{ filterFactory({
id: '1', conditions: [
leftValue: left, {
rightValue: right, id: '1',
operator: { operation: 'notContains', type: 'array', rightType: 'any' }, leftValue: left,
}, rightValue: right,
], operator: { operation: 'notContains', type: 'array', rightType: 'any' },
}), },
); ],
expect(result).toBe(expected); }),
}); );
expect(result).toBe(expected);
},
);
it.each([ it.each([
{ left: ['foo', 'bar'], right: 2, expected: true }, { left: ['foo', 'bar'], right: 2, expected: true },
{ left: [], right: 0, expected: true }, { left: [], right: 0, expected: true },
{ left: ['foo', 'bar'], right: 1, expected: false }, { left: ['foo', 'bar'], right: 1, expected: false },
])('array:lengthEquals($left,$right) === $expected', ({ left, right, expected }) => { ] as Tests)(
const result = executeFilter( 'array:lengthEquals($left,$right) === $expected',
filterFactory({ ({ left, right, expected }) => {
conditions: [ const result = executeFilter(
{ filterFactory({
id: '1', conditions: [
leftValue: left, {
rightValue: right, id: '1',
operator: { operation: 'lengthEquals', type: 'array', rightType: 'number' }, leftValue: left,
}, rightValue: right,
], operator: { operation: 'lengthEquals', type: 'array', rightType: 'number' },
}), },
); ],
expect(result).toBe(expected); }),
}); );
expect(result).toBe(expected);
},
);
it.each([ it.each([
{ left: ['foo', 'bar'], right: 2, expected: false }, { left: ['foo', 'bar'], right: 2, expected: false },
{ left: [], right: 0, expected: false }, { left: [], right: 0, expected: false },
{ left: ['foo', 'bar'], right: 1, expected: true }, { left: ['foo', 'bar'], right: 1, expected: true },
])('array:lengthNotEquals($left,$right) === $expected', ({ left, right, expected }) => { ] as Tests)(
const result = executeFilter( 'array:lengthNotEquals($left,$right) === $expected',
filterFactory({ ({ left, right, expected }) => {
conditions: [ const result = executeFilter(
{ filterFactory({
id: '1', conditions: [
leftValue: left, {
rightValue: right, id: '1',
operator: { operation: 'lengthNotEquals', type: 'array', rightType: 'number' }, leftValue: left,
}, rightValue: right,
], operator: { operation: 'lengthNotEquals', type: 'array', rightType: 'number' },
}), },
); ],
expect(result).toBe(expected); }),
}); );
expect(result).toBe(expected);
},
);
it.each([ it.each([
{ left: ['foo', 'bar'], right: 2, expected: false }, { left: ['foo', 'bar'], right: 2, expected: false },
{ left: [], right: 0, expected: false }, { left: [], right: 0, expected: false },
{ left: ['foo', 'bar'], right: 1, expected: true }, { left: ['foo', 'bar'], right: 1, expected: true },
])('array:lengthGt($left,$right) === $expected', ({ left, right, expected }) => { ] as Tests)('array:lengthGt($left,$right) === $expected', ({ left, right, expected }) => {
const result = executeFilter( const result = executeFilter(
filterFactory({ filterFactory({
conditions: [ conditions: [
@ -1135,7 +1153,7 @@ describe('FilterParameter', () => {
{ left: [], right: 0, expected: false }, { left: [], right: 0, expected: false },
{ left: ['foo', 'bar'], right: 1, expected: false }, { left: ['foo', 'bar'], right: 1, expected: false },
{ left: ['foo', 'bar'], right: 3, expected: true }, { left: ['foo', 'bar'], right: 3, expected: true },
])('array:lengthLt($left,$right) === $expected', ({ left, right, expected }) => { ] as Tests)('array:lengthLt($left,$right) === $expected', ({ left, right, expected }) => {
const result = executeFilter( const result = executeFilter(
filterFactory({ filterFactory({
conditions: [ conditions: [
@ -1156,7 +1174,7 @@ describe('FilterParameter', () => {
{ left: [], right: 0, expected: true }, { left: [], right: 0, expected: true },
{ left: ['foo', 'bar'], right: 1, expected: true }, { left: ['foo', 'bar'], right: 1, expected: true },
{ left: ['foo', 'bar'], right: 3, expected: false }, { left: ['foo', 'bar'], right: 3, expected: false },
])('array:lengthGte($left,$right) === $expected', ({ left, right, expected }) => { ] as Tests)('array:lengthGte($left,$right) === $expected', ({ left, right, expected }) => {
const result = executeFilter( const result = executeFilter(
filterFactory({ filterFactory({
conditions: [ conditions: [
@ -1177,7 +1195,7 @@ describe('FilterParameter', () => {
{ left: [], right: 0, expected: true }, { left: [], right: 0, expected: true },
{ left: ['foo', 'bar'], right: 1, expected: false }, { left: ['foo', 'bar'], right: 1, expected: false },
{ left: ['foo', 'bar'], right: 3, expected: true }, { left: ['foo', 'bar'], right: 3, expected: true },
])('array:lengthLte($left,$right) === $expected', ({ left, right, expected }) => { ] as Tests)('array:lengthLte($left,$right) === $expected', ({ left, right, expected }) => {
const result = executeFilter( const result = executeFilter(
filterFactory({ filterFactory({
conditions: [ conditions: [

View file

@ -9,7 +9,7 @@ import {
} from '@/TelemetryHelpers'; } from '@/TelemetryHelpers';
import { nodeTypes } from './ExpressionExtensions/Helpers'; import { nodeTypes } from './ExpressionExtensions/Helpers';
import * as nodeHelpers from '@/NodeHelpers'; import * as nodeHelpers from '@/NodeHelpers';
import type { IWorkflowBase } from '@/Interfaces'; import { NodeConnectionType, type IWorkflowBase } from '@/Interfaces';
import { STICKY_NODE_TYPE } from '@/Constants'; import { STICKY_NODE_TYPE } from '@/Constants';
import { ApplicationError } from '@/errors'; import { ApplicationError } from '@/errors';
import { randomInt } from '@/utils'; import { randomInt } from '@/utils';
@ -111,7 +111,7 @@ describe('generateNodesGraph', () => {
], ],
connections: { connections: {
'When clicking "Execute Workflow"': { 'When clicking "Execute Workflow"': {
main: [[{ node: 'Google Sheets', type: 'main', index: 0 }]], main: [[{ node: 'Google Sheets', type: NodeConnectionType.Main, index: 0 }]],
}, },
}, },
settings: { executionOrder: 'v1' }, settings: { executionOrder: 'v1' },
@ -215,7 +215,7 @@ describe('generateNodesGraph', () => {
], ],
connections: { connections: {
'When clicking "Execute Workflow"': { 'When clicking "Execute Workflow"': {
main: [[{ node: 'Google Sheets', type: 'main', index: 0 }]], main: [[{ node: 'Google Sheets', type: NodeConnectionType.Main, index: 0 }]],
}, },
}, },
settings: { executionOrder: 'v1' }, settings: { executionOrder: 'v1' },
@ -291,7 +291,7 @@ describe('generateNodesGraph', () => {
], ],
connections: { connections: {
'When clicking "Execute Workflow"': { 'When clicking "Execute Workflow"': {
main: [[{ node: 'Google Sheets', type: 'main', index: 0 }]], main: [[{ node: 'Google Sheets', type: NodeConnectionType.Main, index: 0 }]],
}, },
}, },
settings: { executionOrder: 'v1' }, settings: { executionOrder: 'v1' },
@ -369,7 +369,7 @@ describe('generateNodesGraph', () => {
], ],
connections: { connections: {
'When clicking "Execute Workflow"': { 'When clicking "Execute Workflow"': {
main: [[{ node: 'Google Sheets', type: 'main', index: 0 }]], main: [[{ node: 'Google Sheets', type: NodeConnectionType.Main, index: 0 }]],
}, },
}, },
settings: { executionOrder: 'v1' }, settings: { executionOrder: 'v1' },
@ -701,7 +701,7 @@ describe('generateNodesGraph', () => {
[ [
{ {
node: 'Chain', node: 'Chain',
type: 'main', type: NodeConnectionType.Main,
index: 0, index: 0,
}, },
], ],
@ -712,7 +712,7 @@ describe('generateNodesGraph', () => {
[ [
{ {
node: 'Chain', node: 'Chain',
type: 'ai_languageModel', type: NodeConnectionType.AiLanguageModel,
index: 0, index: 0,
}, },
], ],

View file

@ -1,179 +1,212 @@
import { getValueDescription, validateFieldType } from '@/TypeValidation';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { getValueDescription, validateFieldType } from '@/TypeValidation';
const VALID_ISO_DATES = [
'1994-11-05T08:15:30-05:00',
'1994-11-05T13:15:30Z',
'1997-07-16T19:20+01:00',
'1997-07-16T19:20:30+01:00',
'1997-07-16T19:20:30.45+01:00',
'2018-05-16',
'1972-06-30T23:59:40Z',
'2019-03-26T14:00:00.9Z',
'2019-03-26T14:00:00.4999Z',
'2023-05-17T10:52:32+0000',
'2023-05-17T10:52:32+0000',
];
const VALID_HTTP_DATES = [
'Wed, 21 Oct 2015 07:28:00 GMT',
'Wed, 01 Jun 2022 08:00:00 GMT',
'Tue, 15 Nov 1994 12:45:26 GMT',
'Wed, 1 Jun 2022 08:00:00 GMT',
];
const VALID_RFC_DATES = [
'Tue, 04 Jun 2013 07:40:03 -0400',
'Tue, 4 Jun 2013 02:24:39 +0530',
'Wed, 17 May 2023 10:52:32 +0000',
];
const VALID_SQL_DATES = ['2008-11-11', '2008-11-11 13:23:44'];
const OTHER_VALID_DATES = [
'Wed, 17 May 2023 10:52:32',
'SMT, 17 May 2023 10:52:32',
'1-Feb-2024',
new Date(),
DateTime.now(),
];
const INVALID_DATES = [
'1994-11-05M08:15:30-05:00',
'18-05-2020',
'',
'1685084980', // We are not supporting timestamps
'1685085012135',
1685084980,
1685085012135,
true,
[],
];
describe('Type Validation', () => { describe('Type Validation', () => {
describe('Dates', () => { describe('Dates', () => {
test.each(VALID_ISO_DATES)('should validate and cast ISO date "%s"', (date) => { test('should validate and cast ISO dates', () => {
const validationResult = validateFieldType('date', date, 'dateTime'); const VALID_ISO_DATES = [
expect(validationResult.valid).toBe(true); '1994-11-05T08:15:30-05:00',
expect((validationResult.newValue as DateTime).isValid).toBe(true); '1994-11-05T13:15:30Z',
'1997-07-16T19:20+01:00',
'1997-07-16T19:20:30+01:00',
'1997-07-16T19:20:30.45+01:00',
'2018-05-16',
'1972-06-30T23:59:40Z',
'2019-03-26T14:00:00.9Z',
'2019-03-26T14:00:00.4999Z',
'2023-05-17T10:52:32+0000',
'2023-05-17T10:52:32+0000',
];
VALID_ISO_DATES.forEach((date) =>
expect(validateFieldType('date', date, 'dateTime')).toEqual({
valid: true,
newValue: expect.any(DateTime),
}),
);
}); });
test.each(VALID_RFC_DATES)('should validate and cast RFC2822 date "%s"', (date) => { test('should validate and cast RFC2822 dates', () => {
const validationResult = validateFieldType('date', date, 'dateTime'); const VALID_RFC_DATES = [
expect(validationResult.valid).toBe(true); 'Tue, 04 Jun 2013 07:40:03 -0400',
expect((validationResult.newValue as DateTime).isValid).toBe(true); 'Tue, 4 Jun 2013 02:24:39 +0530',
'Wed, 17 May 2023 10:52:32 +0000',
];
VALID_RFC_DATES.forEach((date) =>
expect(validateFieldType('date', date, 'dateTime')).toEqual({
valid: true,
newValue: expect.any(DateTime),
}),
);
}); });
test.each(VALID_HTTP_DATES)('should validate and cast HTTP date "%s"', (date) => { test('should validate and cast HTTP dates', () => {
const validationResult = validateFieldType('date', date, 'dateTime'); const VALID_HTTP_DATES = [
expect(validationResult.valid).toBe(true); 'Wed, 21 Oct 2015 07:28:00 GMT',
expect((validationResult.newValue as DateTime).isValid).toBe(true); 'Wed, 01 Jun 2022 08:00:00 GMT',
'Tue, 15 Nov 1994 12:45:26 GMT',
'Wed, 1 Jun 2022 08:00:00 GMT',
];
VALID_HTTP_DATES.forEach((date) =>
expect(validateFieldType('date', date, 'dateTime')).toEqual({
valid: true,
newValue: expect.any(DateTime),
}),
);
}); });
test.each(VALID_SQL_DATES)('should validate and cast SQL date "%s"', (date) => { test('should validate and cast SQL dates', () => {
const validationResult = validateFieldType('date', date, 'dateTime'); const VALID_SQL_DATES = ['2008-11-11', '2008-11-11 13:23:44'];
expect(validationResult.valid).toBe(true); VALID_SQL_DATES.forEach((date) =>
expect((validationResult.newValue as DateTime).isValid).toBe(true); expect(validateFieldType('date', date, 'dateTime')).toEqual({
valid: true,
newValue: expect.any(DateTime),
}),
);
}); });
test.each(OTHER_VALID_DATES)('should validate and cast date "%s"', (date) => { test('should validate and cast other valid dates', () => {
const validationResult = validateFieldType('date', date, 'dateTime'); const OTHER_VALID_DATES = [
expect(validationResult.valid).toBe(true); 'Wed, 17 May 2023 10:52:32',
expect((validationResult.newValue as DateTime).isValid).toBe(true); 'SMT, 17 May 2023 10:52:32',
'1-Feb-2024',
new Date(),
DateTime.now(),
];
OTHER_VALID_DATES.forEach((date) =>
expect(validateFieldType('date', date, 'dateTime')).toEqual({
valid: true,
newValue: expect.any(DateTime),
}),
);
}); });
test.each(INVALID_DATES)('should not validate invalid date "%s"', (date) => { test('should not validate invalid dates', () => {
const validationResult = validateFieldType('date', date, 'dateTime'); const INVALID_DATES = [
expect(validationResult.valid).toBe(false); '1994-11-05M08:15:30-05:00',
'18-05-2020',
'',
'1685084980', // We are not supporting timestamps
'1685085012135',
1685084980,
1685085012135,
true,
[],
];
INVALID_DATES.forEach((date) =>
expect(validateFieldType('date', date, 'dateTime').valid).toBe(false),
);
}); });
}); });
it('should validate boolean values properly', () => { it('should validate boolean values properly', () => {
expect(validateFieldType('boolean', 'true', 'boolean').newValue).toBe(true); const TRUE_VALUES = ['true', 'TRUE', 1, '1', '01'];
expect(validateFieldType('boolean', 'TRUE', 'boolean').newValue).toBe(true); TRUE_VALUES.forEach((value) =>
expect(validateFieldType('boolean', 1, 'boolean').newValue).toBe(true); expect(validateFieldType('boolean', value, 'boolean')).toEqual({
expect(validateFieldType('boolean', '1', 'boolean').newValue).toBe(true); valid: true,
expect(validateFieldType('boolean', '01', 'boolean').newValue).toBe(true); newValue: true,
expect(validateFieldType('boolean', 'false', 'boolean').newValue).toBe(false); }),
expect(validateFieldType('boolean', 'FALSE', 'boolean').newValue).toBe(false); );
expect(validateFieldType('boolean', '0', 'boolean').newValue).toBe(false);
expect(validateFieldType('boolean', '000', 'boolean').newValue).toBe(false); const FALSE_VALUES = ['false', 'FALSE', 0, '0', '000', '0000'];
expect(validateFieldType('boolean', '0000', 'boolean').newValue).toBe(false); FALSE_VALUES.forEach((value) =>
expect(validateFieldType('boolean', 0, 'boolean').newValue).toBe(false); expect(validateFieldType('boolean', value, 'boolean')).toEqual({
valid: true,
newValue: false,
}),
);
}); });
it('should not validate invalid boolean values', () => { it('should not validate invalid boolean values', () => {
expect(validateFieldType('boolean', 'tru', 'boolean').valid).toBe(false); const INVALID_VALUES = ['tru', 'fals', 1111, 2, -1, 'yes', 'no'];
expect(validateFieldType('boolean', 'fals', 'boolean').valid).toBe(false); INVALID_VALUES.forEach((value) =>
expect(validateFieldType('boolean', 1111, 'boolean').valid).toBe(false); expect(validateFieldType('boolean', value, 'boolean').valid).toEqual(false),
expect(validateFieldType('boolean', 2, 'boolean').valid).toBe(false); );
expect(validateFieldType('boolean', -1, 'boolean').valid).toBe(false);
expect(validateFieldType('boolean', 'yes', 'boolean').valid).toBe(false);
expect(validateFieldType('boolean', 'no', 'boolean').valid).toBe(false);
}); });
it('should validate and cast numbers', () => { it('should validate and cast numbers', () => {
expect(validateFieldType('number', '1', 'number').newValue).toBe(1); const VALID_NUMBERS = [
expect(validateFieldType('number', '-1', 'number').newValue).toBe(-1); ['1', 1],
expect(validateFieldType('number', '1.1', 'number').newValue).toBe(1.1); ['-1', -1],
expect(validateFieldType('number', '-1.1', 'number').newValue).toBe(-1.1); ['1.1', 1.1],
expect(validateFieldType('number', 1, 'number').newValue).toBe(1); ['-1.1', -1.1],
expect(validateFieldType('number', 'A', 'number').valid).toBe(false); [1, 1],
expect(validateFieldType('number', '1,1', 'number').valid).toBe(false); [true, 1],
expect(validateFieldType('number', true, 'number').valid).toBe(true); ];
expect(validateFieldType('number', '1972-06-30T23:59:40Z', 'number').valid).toBe(false); VALID_NUMBERS.forEach(([value, expected]) =>
expect(validateFieldType('number', [1, 2], 'number').valid).toBe(false); expect(validateFieldType('number', value, 'number')).toEqual({
valid: true,
newValue: expected,
}),
);
const INVALID_NUMBERS = ['A', '1,1', '1972-06-30T23:59:40Z', [1, 2]];
INVALID_NUMBERS.forEach((value) =>
expect(validateFieldType('number', value, 'number').valid).toEqual(false),
);
}); });
it('should validate and cast JSON properly', () => { it('should validate and cast JSON properly', () => {
expect(validateFieldType('json', '{"a": 1}', 'object').newValue).toEqual({ a: 1 }); const VALID_OBJECTS = [
expect( ['{"a": 1}', { a: 1 }],
validateFieldType('json', '{"a": 1, "b": { "c": 10, "d": "test"}}', 'object').valid, ['{"a": 1, "b": { "c": 10, "d": "test"}}', { a: 1, b: { c: 10, d: 'test' } }],
).toEqual(true); [{ name: 'John' }, { name: 'John' }],
expect(validateFieldType('json', { name: 'John' }, 'object').valid).toEqual(true); [
expect(
validateFieldType(
'json',
{ name: 'John', address: { street: 'Via Roma', city: 'Milano' } }, { name: 'John', address: { street: 'Via Roma', city: 'Milano' } },
'object', { name: 'John', address: { street: 'Via Roma', city: 'Milano' } },
).valid, ],
).toEqual(true); ];
// Invalid value: VALID_OBJECTS.forEach(([value, expected]) =>
expect(validateFieldType('json', ['one', 'two'], 'object').valid).toEqual(false); expect(validateFieldType('json', value, 'object')).toEqual({
expect(validateFieldType('json', ['one', 'two'], 'object').valid).toEqual(false); valid: true,
expect(validateFieldType('json', '1', 'object').valid).toEqual(false); newValue: expected,
expect(validateFieldType('json', '[1]', 'object').valid).toEqual(false); }),
expect(validateFieldType('json', '1.1', 'object').valid).toEqual(false); );
expect(validateFieldType('json', 1.1, 'object').valid).toEqual(false);
expect(validateFieldType('json', '"a"', 'object').valid).toEqual(false); const INVALID_JSON = [
expect(validateFieldType('json', '{a: 1}', 'object').valid).toEqual(false); ['one', 'two'],
expect(validateFieldType('json', '["apples", "oranges"]', 'object').valid).toEqual(false); '1',
expect(validateFieldType('json', [{ name: 'john' }, { name: 'bob' }], 'object').valid).toEqual( '[1]',
false, '1.1',
1.1,
'"a"',
'{a: 1}',
'["apples", "oranges"]',
[{ name: 'john' }, { name: 'bob' }],
'[ { name: "john" }, { name: "bob" } ]',
];
INVALID_JSON.forEach((value) =>
expect(validateFieldType('json', value, 'object').valid).toEqual(false),
); );
expect(
validateFieldType('json', '[ { name: "john" }, { name: "bob" } ]', 'object').valid,
).toEqual(false);
}); });
it('should validate and cast arrays properly', () => { it('should validate and cast arrays properly', () => {
expect(validateFieldType('array', '["apples", "oranges"]', 'array').newValue).toEqual([ const VALID_ARRAYS = [
'apples', ['["apples", "oranges"]', ['apples', 'oranges']],
'oranges', ['[1]', [1]],
]); ['[1, 2]', [1, 2]],
expect(validateFieldType('array', '[1]', 'array').newValue).toEqual([1]); ];
expect(validateFieldType('array', '[1, 2]', 'array').newValue).toEqual([1, 2]); VALID_ARRAYS.forEach(([value, expected]) =>
// Invalid values: expect(validateFieldType('array', value, 'array')).toEqual({
expect(validateFieldType('array', '"apples", "oranges"', 'array').valid).toEqual(false); valid: true,
expect(validateFieldType('array', '1', 'array').valid).toEqual(false); newValue: expected,
expect(validateFieldType('array', '1.1', 'array').valid).toEqual(false); }),
expect(validateFieldType('array', '1, 2', 'array').valid).toEqual(false); );
expect(validateFieldType('array', '1. 2. 3', 'array').valid).toEqual(false);
expect(validateFieldType('array', '[1, 2, 3', 'array').valid).toEqual(false); const INVALID_ARRAYS = [
expect(validateFieldType('array', '1, 2, 3]', 'array').valid).toEqual(false); '"apples", "oranges"',
expect(validateFieldType('array', '{1, 2, {3, 4}, 5}', 'array').valid).toEqual(false); '1',
expect(validateFieldType('array', '1, 2, {3, 4}, 5', 'array').valid).toEqual(false); '1.1',
expect(validateFieldType('array', { name: 'John' }, 'array').valid).toEqual(false); '1, 2',
'1. 2. 3',
'[1, 2, 3',
'1, 2, 3]',
'{1, 2, {3, 4}, 5}',
'1, 2, {3, 4}, 5',
{ name: 'John' },
];
INVALID_ARRAYS.forEach((value) =>
expect(validateFieldType('array', value, 'array').valid).toEqual(false),
);
}); });
it('should validate options properly', () => { it('should validate options properly', () => {
@ -196,19 +229,27 @@ describe('Type Validation', () => {
}); });
it('should validate and cast time properly', () => { it('should validate and cast time properly', () => {
expect(validateFieldType('time', '23:23', 'time').valid).toEqual(true); const VALID_TIMES = [
expect(validateFieldType('time', '23:23:23', 'time').valid).toEqual(true); ['23:23', '23:23'],
expect(validateFieldType('time', '23:23:23+1000', 'time').valid).toEqual(true); ['23:23:23', '23:23:23'],
expect(validateFieldType('time', '23:23:23-1000', 'time').valid).toEqual(true); ['23:23:23+1000', '23:23:23+1000'],
expect(validateFieldType('time', '22:00:00+01:00', 'time').valid).toEqual(true); ['23:23:23-1000', '23:23:23-1000'],
expect(validateFieldType('time', '22:00:00-01:00', 'time').valid).toEqual(true); ['22:00:00+01:00', '22:00:00+01:00'],
expect(validateFieldType('time', '22:00:00+01', 'time').valid).toEqual(true); ['22:00:00-01:00', '22:00:00-01:00'],
expect(validateFieldType('time', '22:00:00-01', 'time').valid).toEqual(true); ['22:00:00+01', '22:00:00+01'],
expect(validateFieldType('time', '23:23:23:23', 'time').valid).toEqual(false); ['22:00:00-01', '22:00:00-01'],
expect(validateFieldType('time', '23', 'time').valid).toEqual(false); ];
expect(validateFieldType('time', 'foo', 'time').valid).toEqual(false); VALID_TIMES.forEach(([value, expected]) =>
expect(validateFieldType('time', '23:23:', 'time').valid).toEqual(false); expect(validateFieldType('time', value, 'time')).toEqual({
expect(validateFieldType('time', '23::23::23', 'time').valid).toEqual(false); valid: true,
newValue: expected,
}),
);
const INVALID_TIMES = ['23:23:23:23', '23', 'foo', '23:23:', '23::23::23'];
INVALID_TIMES.forEach((value) =>
expect(validateFieldType('time', value, 'time').valid).toEqual(false),
);
}); });
describe('options', () => { describe('options', () => {
@ -224,9 +265,15 @@ describe('Type Validation', () => {
describe('parseStrings=true', () => { describe('parseStrings=true', () => {
it('should parse strings from other types', () => { it('should parse strings from other types', () => {
const options = { parseStrings: true }; const options = { parseStrings: true };
expect(validateFieldType('test', 42, 'string').newValue).toBe(42); expect(validateFieldType('test', 42, 'string')).toEqual({ valid: true, newValue: 42 });
expect(validateFieldType('test', 42, 'string', options).newValue).toBe('42'); expect(validateFieldType('test', 42, 'string', options)).toEqual({
expect(validateFieldType('test', true, 'string', options).newValue).toBe('true'); valid: true,
newValue: '42',
});
expect(validateFieldType('test', true, 'string', options)).toEqual({
valid: true,
newValue: 'true',
});
}); });
}); });
}); });