|
|
@@ -0,0 +1,474 @@
|
|
|
+/**
|
|
|
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
|
+ * SPDX-License-Identifier: MIT
|
|
|
+ */
|
|
|
+
|
|
|
+import { describe, expect, it } from 'vitest';
|
|
|
+import { WorkflowVariableType } from '@flowgram.ai/runtime-interface';
|
|
|
+
|
|
|
+import { WorkflowRuntimeType } from './runtime-type';
|
|
|
+
|
|
|
+describe('WorkflowRuntimeType', () => {
|
|
|
+ describe('getWorkflowType', () => {
|
|
|
+ describe('null and undefined values', () => {
|
|
|
+ it('should return Null for null value', () => {
|
|
|
+ const result = WorkflowRuntimeType.getWorkflowType(null);
|
|
|
+ expect(result).toBe(WorkflowVariableType.Null);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return Null for undefined value', () => {
|
|
|
+ const result = WorkflowRuntimeType.getWorkflowType(undefined);
|
|
|
+ expect(result).toBe(WorkflowVariableType.Null);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return Null when no parameter is passed', () => {
|
|
|
+ const result = WorkflowRuntimeType.getWorkflowType();
|
|
|
+ expect(result).toBe(WorkflowVariableType.Null);
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('string values', () => {
|
|
|
+ it('should return String for string value', () => {
|
|
|
+ const result = WorkflowRuntimeType.getWorkflowType('hello');
|
|
|
+ expect(result).toBe(WorkflowVariableType.String);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return String for empty string', () => {
|
|
|
+ const result = WorkflowRuntimeType.getWorkflowType('');
|
|
|
+ expect(result).toBe(WorkflowVariableType.String);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return String for string with spaces', () => {
|
|
|
+ const result = WorkflowRuntimeType.getWorkflowType(' hello world ');
|
|
|
+ expect(result).toBe(WorkflowVariableType.String);
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('boolean values', () => {
|
|
|
+ it('should return Boolean for true', () => {
|
|
|
+ const result = WorkflowRuntimeType.getWorkflowType(true);
|
|
|
+ expect(result).toBe(WorkflowVariableType.Boolean);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return Boolean for false', () => {
|
|
|
+ const result = WorkflowRuntimeType.getWorkflowType(false);
|
|
|
+ expect(result).toBe(WorkflowVariableType.Boolean);
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('number values', () => {
|
|
|
+ it('should return Integer for positive integer', () => {
|
|
|
+ const result = WorkflowRuntimeType.getWorkflowType(42);
|
|
|
+ expect(result).toBe(WorkflowVariableType.Integer);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return Integer for negative integer', () => {
|
|
|
+ const result = WorkflowRuntimeType.getWorkflowType(-42);
|
|
|
+ expect(result).toBe(WorkflowVariableType.Integer);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return Integer for zero', () => {
|
|
|
+ const result = WorkflowRuntimeType.getWorkflowType(0);
|
|
|
+ expect(result).toBe(WorkflowVariableType.Integer);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return Number for positive float', () => {
|
|
|
+ const result = WorkflowRuntimeType.getWorkflowType(3.14);
|
|
|
+ expect(result).toBe(WorkflowVariableType.Number);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return Number for negative float', () => {
|
|
|
+ const result = WorkflowRuntimeType.getWorkflowType(-3.14);
|
|
|
+ expect(result).toBe(WorkflowVariableType.Number);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return Number for very small decimal', () => {
|
|
|
+ const result = WorkflowRuntimeType.getWorkflowType(0.001);
|
|
|
+ expect(result).toBe(WorkflowVariableType.Number);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return Number for Infinity', () => {
|
|
|
+ const result = WorkflowRuntimeType.getWorkflowType(Infinity);
|
|
|
+ expect(result).toBe(WorkflowVariableType.Number);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return Number for -Infinity', () => {
|
|
|
+ const result = WorkflowRuntimeType.getWorkflowType(-Infinity);
|
|
|
+ expect(result).toBe(WorkflowVariableType.Number);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return Number for NaN', () => {
|
|
|
+ const result = WorkflowRuntimeType.getWorkflowType(NaN);
|
|
|
+ expect(result).toBe(WorkflowVariableType.Number);
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('array values', () => {
|
|
|
+ it('should return Array for empty array', () => {
|
|
|
+ const result = WorkflowRuntimeType.getWorkflowType([]);
|
|
|
+ expect(result).toBe(WorkflowVariableType.Array);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return Array for array with numbers', () => {
|
|
|
+ const result = WorkflowRuntimeType.getWorkflowType([1, 2, 3]);
|
|
|
+ expect(result).toBe(WorkflowVariableType.Array);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return Array for array with strings', () => {
|
|
|
+ const result = WorkflowRuntimeType.getWorkflowType(['a', 'b', 'c']);
|
|
|
+ expect(result).toBe(WorkflowVariableType.Array);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return Array for mixed type array', () => {
|
|
|
+ const result = WorkflowRuntimeType.getWorkflowType([1, 'hello', true, null]);
|
|
|
+ expect(result).toBe(WorkflowVariableType.Array);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return Array for nested arrays', () => {
|
|
|
+ const result = WorkflowRuntimeType.getWorkflowType([
|
|
|
+ [1, 2],
|
|
|
+ [3, 4],
|
|
|
+ ]);
|
|
|
+ expect(result).toBe(WorkflowVariableType.Array);
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('object values', () => {
|
|
|
+ it('should return Object for empty object', () => {
|
|
|
+ const result = WorkflowRuntimeType.getWorkflowType({});
|
|
|
+ expect(result).toBe(WorkflowVariableType.Object);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return Object for simple object', () => {
|
|
|
+ const result = WorkflowRuntimeType.getWorkflowType({ name: 'John', age: 30 });
|
|
|
+ expect(result).toBe(WorkflowVariableType.Object);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return Object for nested object', () => {
|
|
|
+ const result = WorkflowRuntimeType.getWorkflowType({
|
|
|
+ user: { name: 'John', profile: { age: 30 } },
|
|
|
+ });
|
|
|
+ expect(result).toBe(WorkflowVariableType.Object);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return Object for Date object', () => {
|
|
|
+ const result = WorkflowRuntimeType.getWorkflowType(new Date());
|
|
|
+ expect(result).toBe(WorkflowVariableType.Object);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return Object for RegExp object', () => {
|
|
|
+ const result = WorkflowRuntimeType.getWorkflowType(/test/);
|
|
|
+ expect(result).toBe(WorkflowVariableType.Object);
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('unsupported types', () => {
|
|
|
+ it('should return null for function', () => {
|
|
|
+ const result = WorkflowRuntimeType.getWorkflowType(() => {});
|
|
|
+ expect(result).toBe(null);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return null for symbol', () => {
|
|
|
+ const result = WorkflowRuntimeType.getWorkflowType(Symbol('test'));
|
|
|
+ expect(result).toBe(null);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return null for bigint', () => {
|
|
|
+ const result = WorkflowRuntimeType.getWorkflowType(BigInt(123));
|
|
|
+ expect(result).toBe(null);
|
|
|
+ });
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('isMatchWorkflowType', () => {
|
|
|
+ describe('matching types', () => {
|
|
|
+ it('should return true for matching string type', () => {
|
|
|
+ const result = WorkflowRuntimeType.isMatchWorkflowType(
|
|
|
+ 'hello',
|
|
|
+ WorkflowVariableType.String
|
|
|
+ );
|
|
|
+ expect(result).toBe(true);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return true for matching boolean type', () => {
|
|
|
+ const result = WorkflowRuntimeType.isMatchWorkflowType(true, WorkflowVariableType.Boolean);
|
|
|
+ expect(result).toBe(true);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return true for matching integer type', () => {
|
|
|
+ const result = WorkflowRuntimeType.isMatchWorkflowType(42, WorkflowVariableType.Integer);
|
|
|
+ expect(result).toBe(true);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return true for matching number type', () => {
|
|
|
+ const result = WorkflowRuntimeType.isMatchWorkflowType(3.14, WorkflowVariableType.Number);
|
|
|
+ expect(result).toBe(true);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return true for matching array type', () => {
|
|
|
+ const result = WorkflowRuntimeType.isMatchWorkflowType(
|
|
|
+ [1, 2, 3],
|
|
|
+ WorkflowVariableType.Array
|
|
|
+ );
|
|
|
+ expect(result).toBe(true);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return true for matching object type', () => {
|
|
|
+ const result = WorkflowRuntimeType.isMatchWorkflowType(
|
|
|
+ { name: 'John' },
|
|
|
+ WorkflowVariableType.Object
|
|
|
+ );
|
|
|
+ expect(result).toBe(true);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return true for matching null type', () => {
|
|
|
+ const result = WorkflowRuntimeType.isMatchWorkflowType(null, WorkflowVariableType.Null);
|
|
|
+ expect(result).toBe(true);
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('non-matching types', () => {
|
|
|
+ it('should return false for string vs number type', () => {
|
|
|
+ const result = WorkflowRuntimeType.isMatchWorkflowType(
|
|
|
+ 'hello',
|
|
|
+ WorkflowVariableType.Number
|
|
|
+ );
|
|
|
+ expect(result).toBe(false);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return false for number vs string type', () => {
|
|
|
+ const result = WorkflowRuntimeType.isMatchWorkflowType(42, WorkflowVariableType.String);
|
|
|
+ expect(result).toBe(false);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return false for array vs object type', () => {
|
|
|
+ const result = WorkflowRuntimeType.isMatchWorkflowType(
|
|
|
+ [1, 2, 3],
|
|
|
+ WorkflowVariableType.Object
|
|
|
+ );
|
|
|
+ expect(result).toBe(false);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return false for object vs array type', () => {
|
|
|
+ const result = WorkflowRuntimeType.isMatchWorkflowType(
|
|
|
+ { name: 'John' },
|
|
|
+ WorkflowVariableType.Array
|
|
|
+ );
|
|
|
+ expect(result).toBe(false);
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('unsupported values', () => {
|
|
|
+ it('should return false for function', () => {
|
|
|
+ const result = WorkflowRuntimeType.isMatchWorkflowType(() => {},
|
|
|
+ WorkflowVariableType.Object);
|
|
|
+ expect(result).toBe(false);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return false for symbol', () => {
|
|
|
+ const result = WorkflowRuntimeType.isMatchWorkflowType(
|
|
|
+ Symbol('test'),
|
|
|
+ WorkflowVariableType.String
|
|
|
+ );
|
|
|
+ expect(result).toBe(false);
|
|
|
+ });
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('isTypeEqual', () => {
|
|
|
+ describe('exact type matches', () => {
|
|
|
+ it('should return true for same string types', () => {
|
|
|
+ const result = WorkflowRuntimeType.isTypeEqual(
|
|
|
+ WorkflowVariableType.String,
|
|
|
+ WorkflowVariableType.String
|
|
|
+ );
|
|
|
+ expect(result).toBe(true);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return true for same boolean types', () => {
|
|
|
+ const result = WorkflowRuntimeType.isTypeEqual(
|
|
|
+ WorkflowVariableType.Boolean,
|
|
|
+ WorkflowVariableType.Boolean
|
|
|
+ );
|
|
|
+ expect(result).toBe(true);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return true for same array types', () => {
|
|
|
+ const result = WorkflowRuntimeType.isTypeEqual(
|
|
|
+ WorkflowVariableType.Array,
|
|
|
+ WorkflowVariableType.Array
|
|
|
+ );
|
|
|
+ expect(result).toBe(true);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return true for same object types', () => {
|
|
|
+ const result = WorkflowRuntimeType.isTypeEqual(
|
|
|
+ WorkflowVariableType.Object,
|
|
|
+ WorkflowVariableType.Object
|
|
|
+ );
|
|
|
+ expect(result).toBe(true);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return true for same null types', () => {
|
|
|
+ const result = WorkflowRuntimeType.isTypeEqual(
|
|
|
+ WorkflowVariableType.Null,
|
|
|
+ WorkflowVariableType.Null
|
|
|
+ );
|
|
|
+ expect(result).toBe(true);
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('number and integer equivalence', () => {
|
|
|
+ it('should return true for Number and Integer', () => {
|
|
|
+ const result = WorkflowRuntimeType.isTypeEqual(
|
|
|
+ WorkflowVariableType.Number,
|
|
|
+ WorkflowVariableType.Integer
|
|
|
+ );
|
|
|
+ expect(result).toBe(true);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return true for Integer and Number', () => {
|
|
|
+ const result = WorkflowRuntimeType.isTypeEqual(
|
|
|
+ WorkflowVariableType.Integer,
|
|
|
+ WorkflowVariableType.Number
|
|
|
+ );
|
|
|
+ expect(result).toBe(true);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return true for Number and Number', () => {
|
|
|
+ const result = WorkflowRuntimeType.isTypeEqual(
|
|
|
+ WorkflowVariableType.Number,
|
|
|
+ WorkflowVariableType.Number
|
|
|
+ );
|
|
|
+ expect(result).toBe(true);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return true for Integer and Integer', () => {
|
|
|
+ const result = WorkflowRuntimeType.isTypeEqual(
|
|
|
+ WorkflowVariableType.Integer,
|
|
|
+ WorkflowVariableType.Integer
|
|
|
+ );
|
|
|
+ expect(result).toBe(true);
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('different type mismatches', () => {
|
|
|
+ it('should return false for String and Boolean', () => {
|
|
|
+ const result = WorkflowRuntimeType.isTypeEqual(
|
|
|
+ WorkflowVariableType.String,
|
|
|
+ WorkflowVariableType.Boolean
|
|
|
+ );
|
|
|
+ expect(result).toBe(false);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return false for Array and Object', () => {
|
|
|
+ const result = WorkflowRuntimeType.isTypeEqual(
|
|
|
+ WorkflowVariableType.Array,
|
|
|
+ WorkflowVariableType.Object
|
|
|
+ );
|
|
|
+ expect(result).toBe(false);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return false for String and Number', () => {
|
|
|
+ const result = WorkflowRuntimeType.isTypeEqual(
|
|
|
+ WorkflowVariableType.String,
|
|
|
+ WorkflowVariableType.Number
|
|
|
+ );
|
|
|
+ expect(result).toBe(false);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return false for Boolean and Null', () => {
|
|
|
+ const result = WorkflowRuntimeType.isTypeEqual(
|
|
|
+ WorkflowVariableType.Boolean,
|
|
|
+ WorkflowVariableType.Null
|
|
|
+ );
|
|
|
+ expect(result).toBe(false);
|
|
|
+ });
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('getArrayItemsType', () => {
|
|
|
+ describe('uniform type arrays', () => {
|
|
|
+ it('should return String for all string types', () => {
|
|
|
+ const types = [
|
|
|
+ WorkflowVariableType.String,
|
|
|
+ WorkflowVariableType.String,
|
|
|
+ WorkflowVariableType.String,
|
|
|
+ ];
|
|
|
+ const result = WorkflowRuntimeType.getArrayItemsType(types);
|
|
|
+ expect(result).toBe(WorkflowVariableType.String);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return Number for all number types', () => {
|
|
|
+ const types = [WorkflowVariableType.Number, WorkflowVariableType.Number];
|
|
|
+ const result = WorkflowRuntimeType.getArrayItemsType(types);
|
|
|
+ expect(result).toBe(WorkflowVariableType.Number);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return Boolean for all boolean types', () => {
|
|
|
+ const types = [WorkflowVariableType.Boolean];
|
|
|
+ const result = WorkflowRuntimeType.getArrayItemsType(types);
|
|
|
+ expect(result).toBe(WorkflowVariableType.Boolean);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return Object for all object types', () => {
|
|
|
+ const types = [
|
|
|
+ WorkflowVariableType.Object,
|
|
|
+ WorkflowVariableType.Object,
|
|
|
+ WorkflowVariableType.Object,
|
|
|
+ WorkflowVariableType.Object,
|
|
|
+ ];
|
|
|
+ const result = WorkflowRuntimeType.getArrayItemsType(types);
|
|
|
+ expect(result).toBe(WorkflowVariableType.Object);
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('mixed type arrays', () => {
|
|
|
+ it('should throw error for String and Number mix', () => {
|
|
|
+ const types = [WorkflowVariableType.String, WorkflowVariableType.Number];
|
|
|
+ expect(() => WorkflowRuntimeType.getArrayItemsType(types)).toThrow(
|
|
|
+ 'array items type must be same, expect string, but got number'
|
|
|
+ );
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should throw error for Boolean and String mix', () => {
|
|
|
+ const types = [WorkflowVariableType.Boolean, WorkflowVariableType.String];
|
|
|
+ expect(() => WorkflowRuntimeType.getArrayItemsType(types)).toThrow(
|
|
|
+ 'array items type must be same, expect boolean, but got string'
|
|
|
+ );
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should throw error for Object and Array mix', () => {
|
|
|
+ const types = [WorkflowVariableType.Object, WorkflowVariableType.Array];
|
|
|
+ expect(() => WorkflowRuntimeType.getArrayItemsType(types)).toThrow(
|
|
|
+ 'array items type must be same, expect object, but got array'
|
|
|
+ );
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should throw error for multiple different types', () => {
|
|
|
+ const types = [
|
|
|
+ WorkflowVariableType.String,
|
|
|
+ WorkflowVariableType.Number,
|
|
|
+ WorkflowVariableType.Boolean,
|
|
|
+ ];
|
|
|
+ expect(() => WorkflowRuntimeType.getArrayItemsType(types)).toThrow(
|
|
|
+ 'array items type must be same, expect string, but got number'
|
|
|
+ );
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('edge cases', () => {
|
|
|
+ it('should handle single item array', () => {
|
|
|
+ const types = [WorkflowVariableType.Integer];
|
|
|
+ const result = WorkflowRuntimeType.getArrayItemsType(types);
|
|
|
+ expect(result).toBe(WorkflowVariableType.Integer);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should handle Null types', () => {
|
|
|
+ const types = [WorkflowVariableType.Null, WorkflowVariableType.Null];
|
|
|
+ const result = WorkflowRuntimeType.getArrayItemsType(types);
|
|
|
+ expect(result).toBe(WorkflowVariableType.Null);
|
|
|
+ });
|
|
|
+ });
|
|
|
+ });
|
|
|
+});
|