Agent chat code cleanup (#13088)
This PR addresses feedback from [PR-13061](https://github.com/twentyhq/twenty/pull/13061). - Removed redundant type assertion from `apollo.factory.ts`. - Removed redundant tests: Eliminated test cases that were specifically added to boost coverage metrics rather than testing meaningful functionality
This commit is contained in:
@ -230,7 +230,7 @@ export class ApolloFactory<TCacheShape> implements ApolloManager<TCacheShape> {
|
||||
retryLink,
|
||||
restLink,
|
||||
httpLink,
|
||||
].filter(isDefined) as ApolloLink[],
|
||||
].filter(isDefined),
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
import { InputHotkeyScope } from '@/ui/input/types/InputHotkeyScope';
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
|
||||
import { useScrollWrapperElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperElement';
|
||||
import { AgentChatMessageRole } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/constants/agent-chat-message-role';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { v4 } from 'uuid';
|
||||
import { streamChatResponse } from '../api/streamChatResponse';
|
||||
import { agentChatInputState } from '../states/agentChatInputState';
|
||||
@ -36,22 +37,32 @@ export const useAgentChat = (agentId: string) => {
|
||||
|
||||
const { scrollWrapperHTMLElement } = useScrollWrapperElement(agentId);
|
||||
|
||||
const scrollToBottom = useCallback(() => {
|
||||
const scrollToBottom = () => {
|
||||
scrollWrapperHTMLElement?.scroll({
|
||||
top: scrollWrapperHTMLElement.scrollHeight,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}, [scrollWrapperHTMLElement]);
|
||||
};
|
||||
|
||||
const { data: { threads = [] } = {}, loading: threadsLoading } =
|
||||
useAgentChatThreads(agentId);
|
||||
const currentThreadId = threads[0]?.id;
|
||||
|
||||
const { loading: messagesLoading, refetch: refetchMessages } =
|
||||
useAgentChatMessages(currentThreadId);
|
||||
const {
|
||||
data: messagesData,
|
||||
loading: messagesLoading,
|
||||
refetch: refetchMessages,
|
||||
} = useAgentChatMessages(currentThreadId);
|
||||
|
||||
const isLoading = messagesLoading || threadsLoading || isStreaming;
|
||||
|
||||
if (
|
||||
agentChatMessages.length === 0 &&
|
||||
isDefined(messagesData?.messages?.length)
|
||||
) {
|
||||
setAgentChatMessages(messagesData.messages);
|
||||
}
|
||||
|
||||
const createOptimisticMessages = (content: string): AgentChatMessage[] => {
|
||||
const optimisticUserMessage: OptimisticMessage = {
|
||||
id: v4(),
|
||||
|
||||
@ -2,237 +2,43 @@ import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { getFieldIcon } from '../getFieldIcon';
|
||||
|
||||
describe('getFieldIcon', () => {
|
||||
const UNSUPPORTED_FIELD_TYPES = [
|
||||
'string',
|
||||
'number',
|
||||
'boolean',
|
||||
'object',
|
||||
'array',
|
||||
'unknown',
|
||||
FieldMetadataType.DATE_TIME,
|
||||
FieldMetadataType.EMAILS,
|
||||
FieldMetadataType.PHONES,
|
||||
FieldMetadataType.LINKS,
|
||||
FieldMetadataType.CURRENCY,
|
||||
FieldMetadataType.SELECT,
|
||||
FieldMetadataType.MULTI_SELECT,
|
||||
FieldMetadataType.RELATION,
|
||||
FieldMetadataType.UUID,
|
||||
FieldMetadataType.RAW_JSON,
|
||||
FieldMetadataType.FULL_NAME,
|
||||
FieldMetadataType.ADDRESS,
|
||||
FieldMetadataType.ARRAY,
|
||||
] as const;
|
||||
|
||||
type UnsupportedFieldType = (typeof UNSUPPORTED_FIELD_TYPES)[number];
|
||||
|
||||
describe('FieldMetadataType field types', () => {
|
||||
describe('supported field types', () => {
|
||||
it('should return IconAbc for TEXT field type', () => {
|
||||
const result = getFieldIcon(FieldMetadataType.TEXT);
|
||||
expect(result).toBe('IconAbc');
|
||||
expect(getFieldIcon(FieldMetadataType.TEXT)).toBe('IconAbc');
|
||||
});
|
||||
|
||||
it('should return IconText for NUMBER field type', () => {
|
||||
const result = getFieldIcon(FieldMetadataType.NUMBER);
|
||||
expect(result).toBe('IconText');
|
||||
expect(getFieldIcon(FieldMetadataType.NUMBER)).toBe('IconText');
|
||||
});
|
||||
|
||||
it('should return IconCheckbox for BOOLEAN field type', () => {
|
||||
const result = getFieldIcon(FieldMetadataType.BOOLEAN);
|
||||
expect(result).toBe('IconCheckbox');
|
||||
expect(getFieldIcon(FieldMetadataType.BOOLEAN)).toBe('IconCheckbox');
|
||||
});
|
||||
|
||||
it('should return IconCalendarEvent for DATE field type', () => {
|
||||
const result = getFieldIcon(FieldMetadataType.DATE);
|
||||
expect(result).toBe('IconCalendarEvent');
|
||||
expect(getFieldIcon(FieldMetadataType.DATE)).toBe('IconCalendarEvent');
|
||||
});
|
||||
});
|
||||
|
||||
describe('basic InputSchemaPropertyType field types', () => {
|
||||
it('should return IconQuestionMark for string type', () => {
|
||||
const result = getFieldIcon('string');
|
||||
expect(result).toBe('IconQuestionMark');
|
||||
describe('unsupported and edge cases', () => {
|
||||
it('should return IconQuestionMark for an unsupported field type', () => {
|
||||
expect(getFieldIcon('totally-unknown-type' as any)).toBe(
|
||||
'IconQuestionMark',
|
||||
);
|
||||
});
|
||||
|
||||
it('should return IconQuestionMark for number type', () => {
|
||||
const result = getFieldIcon('number');
|
||||
expect(result).toBe('IconQuestionMark');
|
||||
it('should return IconQuestionMark for undefined', () => {
|
||||
expect(getFieldIcon(undefined)).toBe('IconQuestionMark');
|
||||
});
|
||||
|
||||
it('should return IconQuestionMark for boolean type', () => {
|
||||
const result = getFieldIcon('boolean');
|
||||
expect(result).toBe('IconQuestionMark');
|
||||
it('should return IconQuestionMark for null', () => {
|
||||
expect(getFieldIcon(null as any)).toBe('IconQuestionMark');
|
||||
});
|
||||
|
||||
it('should return IconQuestionMark for object type', () => {
|
||||
const result = getFieldIcon('object');
|
||||
expect(result).toBe('IconQuestionMark');
|
||||
});
|
||||
|
||||
it('should return IconQuestionMark for array type', () => {
|
||||
const result = getFieldIcon('array');
|
||||
expect(result).toBe('IconQuestionMark');
|
||||
});
|
||||
|
||||
it('should return IconQuestionMark for unknown type', () => {
|
||||
const result = getFieldIcon('unknown');
|
||||
expect(result).toBe('IconQuestionMark');
|
||||
it('should return IconQuestionMark for empty string', () => {
|
||||
expect(getFieldIcon('' as any)).toBe('IconQuestionMark');
|
||||
});
|
||||
});
|
||||
|
||||
describe('other FieldMetadataType values', () => {
|
||||
it('should return IconQuestionMark for DATE_TIME field type', () => {
|
||||
const result = getFieldIcon(FieldMetadataType.DATE_TIME);
|
||||
expect(result).toBe('IconQuestionMark');
|
||||
});
|
||||
|
||||
it('should return IconQuestionMark for EMAILS field type', () => {
|
||||
const result = getFieldIcon(FieldMetadataType.EMAILS);
|
||||
expect(result).toBe('IconQuestionMark');
|
||||
});
|
||||
|
||||
it('should return IconQuestionMark for PHONES field type', () => {
|
||||
const result = getFieldIcon(FieldMetadataType.PHONES);
|
||||
expect(result).toBe('IconQuestionMark');
|
||||
});
|
||||
|
||||
it('should return IconQuestionMark for LINKS field type', () => {
|
||||
const result = getFieldIcon(FieldMetadataType.LINKS);
|
||||
expect(result).toBe('IconQuestionMark');
|
||||
});
|
||||
|
||||
it('should return IconQuestionMark for CURRENCY field type', () => {
|
||||
const result = getFieldIcon(FieldMetadataType.CURRENCY);
|
||||
expect(result).toBe('IconQuestionMark');
|
||||
});
|
||||
|
||||
it('should return IconQuestionMark for SELECT field type', () => {
|
||||
const result = getFieldIcon(FieldMetadataType.SELECT);
|
||||
expect(result).toBe('IconQuestionMark');
|
||||
});
|
||||
|
||||
it('should return IconQuestionMark for MULTI_SELECT field type', () => {
|
||||
const result = getFieldIcon(FieldMetadataType.MULTI_SELECT);
|
||||
expect(result).toBe('IconQuestionMark');
|
||||
});
|
||||
|
||||
it('should return IconQuestionMark for RELATION field type', () => {
|
||||
const result = getFieldIcon(FieldMetadataType.RELATION);
|
||||
expect(result).toBe('IconQuestionMark');
|
||||
});
|
||||
|
||||
it('should return IconQuestionMark for UUID field type', () => {
|
||||
const result = getFieldIcon(FieldMetadataType.UUID);
|
||||
expect(result).toBe('IconQuestionMark');
|
||||
});
|
||||
|
||||
it('should return IconQuestionMark for RAW_JSON field type', () => {
|
||||
const result = getFieldIcon(FieldMetadataType.RAW_JSON);
|
||||
expect(result).toBe('IconQuestionMark');
|
||||
});
|
||||
|
||||
it('should return IconQuestionMark for FULL_NAME field type', () => {
|
||||
const result = getFieldIcon(FieldMetadataType.FULL_NAME);
|
||||
expect(result).toBe('IconQuestionMark');
|
||||
});
|
||||
|
||||
it('should return IconQuestionMark for ADDRESS field type', () => {
|
||||
const result = getFieldIcon(FieldMetadataType.ADDRESS);
|
||||
expect(result).toBe('IconQuestionMark');
|
||||
});
|
||||
|
||||
it('should return IconQuestionMark for ARRAY field type', () => {
|
||||
const result = getFieldIcon(FieldMetadataType.ARRAY);
|
||||
expect(result).toBe('IconQuestionMark');
|
||||
});
|
||||
});
|
||||
|
||||
describe('edge cases', () => {
|
||||
it('should return IconQuestionMark for undefined field type', () => {
|
||||
const result = getFieldIcon(undefined);
|
||||
expect(result).toBe('IconQuestionMark');
|
||||
});
|
||||
|
||||
it('should return IconQuestionMark for null field type', () => {
|
||||
const result = getFieldIcon(null as any);
|
||||
expect(result).toBe('IconQuestionMark');
|
||||
});
|
||||
|
||||
it('should return IconQuestionMark for empty string field type', () => {
|
||||
const result = getFieldIcon('' as any);
|
||||
expect(result).toBe('IconQuestionMark');
|
||||
});
|
||||
|
||||
it('should return IconQuestionMark for invalid field type', () => {
|
||||
const result = getFieldIcon('INVALID_TYPE' as any);
|
||||
expect(result).toBe('IconQuestionMark');
|
||||
});
|
||||
});
|
||||
|
||||
describe('icon mapping consistency', () => {
|
||||
it('should return consistent icons for the same field type', () => {
|
||||
const fieldType = FieldMetadataType.TEXT;
|
||||
const result1 = getFieldIcon(fieldType);
|
||||
const result2 = getFieldIcon(fieldType);
|
||||
|
||||
describe('consistency', () => {
|
||||
it('should return the same icon for the same field type', () => {
|
||||
const result1 = getFieldIcon(FieldMetadataType.TEXT);
|
||||
const result2 = getFieldIcon(FieldMetadataType.TEXT);
|
||||
expect(result1).toBe(result2);
|
||||
expect(result1).toBe('IconAbc');
|
||||
});
|
||||
|
||||
it('should have unique icons for different supported field types', () => {
|
||||
const textIcon = getFieldIcon(FieldMetadataType.TEXT);
|
||||
const numberIcon = getFieldIcon(FieldMetadataType.NUMBER);
|
||||
const booleanIcon = getFieldIcon(FieldMetadataType.BOOLEAN);
|
||||
const dateIcon = getFieldIcon(FieldMetadataType.DATE);
|
||||
|
||||
const icons = [textIcon, numberIcon, booleanIcon, dateIcon];
|
||||
const uniqueIcons = new Set(icons);
|
||||
|
||||
expect(uniqueIcons.size).toBe(4);
|
||||
expect(icons).toEqual([
|
||||
'IconAbc',
|
||||
'IconText',
|
||||
'IconCheckbox',
|
||||
'IconCalendarEvent',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return IconQuestionMark for all unsupported field types', () => {
|
||||
const unsupportedTypes = UNSUPPORTED_FIELD_TYPES;
|
||||
|
||||
unsupportedTypes.forEach((fieldType) => {
|
||||
const result = getFieldIcon(fieldType as UnsupportedFieldType);
|
||||
expect(result).toBe('IconQuestionMark');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('function behavior', () => {
|
||||
it('should be a pure function with no side effects', () => {
|
||||
const fieldType = FieldMetadataType.TEXT;
|
||||
const result1 = getFieldIcon(fieldType);
|
||||
const result2 = getFieldIcon(fieldType);
|
||||
const result3 = getFieldIcon(fieldType);
|
||||
|
||||
expect(result1).toBe(result2);
|
||||
expect(result2).toBe(result3);
|
||||
expect(result1).toBe('IconAbc');
|
||||
});
|
||||
|
||||
it('should handle all possible InputSchemaPropertyType values', () => {
|
||||
const allPossibleTypes = [
|
||||
...UNSUPPORTED_FIELD_TYPES,
|
||||
FieldMetadataType.TEXT,
|
||||
FieldMetadataType.NUMBER,
|
||||
FieldMetadataType.BOOLEAN,
|
||||
FieldMetadataType.DATE,
|
||||
] as const;
|
||||
|
||||
allPossibleTypes.forEach((fieldType) => {
|
||||
const result = getFieldIcon(fieldType as UnsupportedFieldType);
|
||||
expect(typeof result).toBe('string');
|
||||
expect(result.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { v4 } from 'uuid';
|
||||
import { WorkflowFormFieldType } from '../../types/WorkflowFormFieldType';
|
||||
import { getDefaultFormFieldSettings } from '../getDefaultFormFieldSettings';
|
||||
|
||||
jest.mock('uuid', () => ({
|
||||
@ -12,206 +11,114 @@ describe('getDefaultFormFieldSettings', () => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('FieldMetadataType.TEXT', () => {
|
||||
it('should return correct default settings for TEXT field type', () => {
|
||||
const result = getDefaultFormFieldSettings(FieldMetadataType.TEXT);
|
||||
|
||||
expect(result).toEqual({
|
||||
id: 'test-uuid-123',
|
||||
name: 'text',
|
||||
label: 'Text',
|
||||
placeholder: 'Enter your text',
|
||||
});
|
||||
it('should return correct default settings for TEXT field type', () => {
|
||||
const result = getDefaultFormFieldSettings(FieldMetadataType.TEXT);
|
||||
expect(result).toEqual({
|
||||
id: 'test-uuid-123',
|
||||
name: 'text',
|
||||
label: 'Text',
|
||||
placeholder: 'Enter your text',
|
||||
});
|
||||
});
|
||||
|
||||
describe('FieldMetadataType.NUMBER', () => {
|
||||
it('should return correct default settings for NUMBER field type', () => {
|
||||
const result = getDefaultFormFieldSettings(FieldMetadataType.NUMBER);
|
||||
|
||||
expect(result).toEqual({
|
||||
id: 'test-uuid-123',
|
||||
name: 'number',
|
||||
label: 'Number',
|
||||
placeholder: '1000',
|
||||
});
|
||||
it('should return correct default settings for NUMBER field type', () => {
|
||||
const result = getDefaultFormFieldSettings(FieldMetadataType.NUMBER);
|
||||
expect(result).toEqual({
|
||||
id: 'test-uuid-123',
|
||||
name: 'number',
|
||||
label: 'Number',
|
||||
placeholder: '1000',
|
||||
});
|
||||
});
|
||||
|
||||
describe('FieldMetadataType.DATE', () => {
|
||||
it('should return correct default settings for DATE field type', () => {
|
||||
const result = getDefaultFormFieldSettings(FieldMetadataType.DATE);
|
||||
|
||||
expect(result).toEqual({
|
||||
id: 'test-uuid-123',
|
||||
name: 'date',
|
||||
label: 'Date',
|
||||
placeholder: 'mm/dd/yyyy',
|
||||
});
|
||||
it('should return correct default settings for DATE field type', () => {
|
||||
const result = getDefaultFormFieldSettings(FieldMetadataType.DATE);
|
||||
expect(result).toEqual({
|
||||
id: 'test-uuid-123',
|
||||
name: 'date',
|
||||
label: 'Date',
|
||||
placeholder: 'mm/dd/yyyy',
|
||||
});
|
||||
});
|
||||
|
||||
describe('RECORD type', () => {
|
||||
it('should return correct default settings for RECORD field type', () => {
|
||||
const result = getDefaultFormFieldSettings('RECORD');
|
||||
|
||||
expect(result).toEqual({
|
||||
id: 'test-uuid-123',
|
||||
name: 'record',
|
||||
label: 'Record',
|
||||
placeholder: 'Select a Company',
|
||||
settings: {
|
||||
objectName: 'company',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('UUID generation', () => {
|
||||
it('should generate unique UUID for each call', () => {
|
||||
getDefaultFormFieldSettings(FieldMetadataType.TEXT);
|
||||
getDefaultFormFieldSettings(FieldMetadataType.NUMBER);
|
||||
|
||||
expect(v4).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('return value structure', () => {
|
||||
it('should return object with required properties for TEXT type', () => {
|
||||
const result = getDefaultFormFieldSettings(FieldMetadataType.TEXT);
|
||||
|
||||
expect(result).toHaveProperty('id');
|
||||
expect(result).toHaveProperty('name');
|
||||
expect(result).toHaveProperty('label');
|
||||
expect(result).toHaveProperty('placeholder');
|
||||
expect(result).not.toHaveProperty('settings');
|
||||
});
|
||||
|
||||
it('should return object with required properties for NUMBER type', () => {
|
||||
const result = getDefaultFormFieldSettings(FieldMetadataType.NUMBER);
|
||||
|
||||
expect(result).toHaveProperty('id');
|
||||
expect(result).toHaveProperty('name');
|
||||
expect(result).toHaveProperty('label');
|
||||
expect(result).toHaveProperty('placeholder');
|
||||
expect(result).not.toHaveProperty('settings');
|
||||
});
|
||||
|
||||
it('should return object with required properties for DATE type', () => {
|
||||
const result = getDefaultFormFieldSettings(FieldMetadataType.DATE);
|
||||
|
||||
expect(result).toHaveProperty('id');
|
||||
expect(result).toHaveProperty('name');
|
||||
expect(result).toHaveProperty('label');
|
||||
expect(result).toHaveProperty('placeholder');
|
||||
expect(result).not.toHaveProperty('settings');
|
||||
});
|
||||
|
||||
it('should return object with required properties for RECORD type', () => {
|
||||
const result = getDefaultFormFieldSettings('RECORD');
|
||||
|
||||
expect(result).toHaveProperty('id');
|
||||
expect(result).toHaveProperty('name');
|
||||
expect(result).toHaveProperty('label');
|
||||
expect(result).toHaveProperty('placeholder');
|
||||
expect(result).toHaveProperty('settings');
|
||||
expect(result.settings).toHaveProperty('objectName');
|
||||
});
|
||||
});
|
||||
|
||||
describe('field type specific values', () => {
|
||||
it('should have correct name values for each field type', () => {
|
||||
const textResult = getDefaultFormFieldSettings(FieldMetadataType.TEXT);
|
||||
const numberResult = getDefaultFormFieldSettings(
|
||||
FieldMetadataType.NUMBER,
|
||||
);
|
||||
const dateResult = getDefaultFormFieldSettings(FieldMetadataType.DATE);
|
||||
const recordResult = getDefaultFormFieldSettings('RECORD');
|
||||
|
||||
expect(textResult.name).toBe('text');
|
||||
expect(numberResult.name).toBe('number');
|
||||
expect(dateResult.name).toBe('date');
|
||||
expect(recordResult.name).toBe('record');
|
||||
});
|
||||
|
||||
it('should have correct label values for each field type', () => {
|
||||
const textResult = getDefaultFormFieldSettings(FieldMetadataType.TEXT);
|
||||
const numberResult = getDefaultFormFieldSettings(
|
||||
FieldMetadataType.NUMBER,
|
||||
);
|
||||
const dateResult = getDefaultFormFieldSettings(FieldMetadataType.DATE);
|
||||
const recordResult = getDefaultFormFieldSettings('RECORD');
|
||||
|
||||
expect(textResult.label).toBe('Text');
|
||||
expect(numberResult.label).toBe('Number');
|
||||
expect(dateResult.label).toBe('Date');
|
||||
expect(recordResult.label).toBe('Record');
|
||||
});
|
||||
|
||||
it('should have correct placeholder values for each field type', () => {
|
||||
const textResult = getDefaultFormFieldSettings(FieldMetadataType.TEXT);
|
||||
const numberResult = getDefaultFormFieldSettings(
|
||||
FieldMetadataType.NUMBER,
|
||||
);
|
||||
const dateResult = getDefaultFormFieldSettings(FieldMetadataType.DATE);
|
||||
const recordResult = getDefaultFormFieldSettings('RECORD');
|
||||
|
||||
expect(textResult.placeholder).toBe('Enter your text');
|
||||
expect(numberResult.placeholder).toBe('1000');
|
||||
expect(dateResult.placeholder).toBe('mm/dd/yyyy');
|
||||
expect(recordResult.placeholder).toBe('Select a Company');
|
||||
});
|
||||
});
|
||||
|
||||
describe('RECORD type specific settings', () => {
|
||||
it('should have correct settings object for RECORD type', () => {
|
||||
const result = getDefaultFormFieldSettings('RECORD');
|
||||
|
||||
expect(result.settings).toEqual({
|
||||
it('should return correct default settings for RECORD field type', () => {
|
||||
const result = getDefaultFormFieldSettings('RECORD');
|
||||
expect(result).toEqual({
|
||||
id: 'test-uuid-123',
|
||||
name: 'record',
|
||||
label: 'Record',
|
||||
placeholder: 'Select a Company',
|
||||
settings: {
|
||||
objectName: 'company',
|
||||
});
|
||||
});
|
||||
|
||||
it('should have objectName set to company for RECORD type', () => {
|
||||
const result = getDefaultFormFieldSettings('RECORD');
|
||||
|
||||
expect(result.settings?.objectName).toBe('company');
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe('consistency', () => {
|
||||
it('should return consistent results for the same field type', () => {
|
||||
const result1 = getDefaultFormFieldSettings(FieldMetadataType.TEXT);
|
||||
const result2 = getDefaultFormFieldSettings(FieldMetadataType.TEXT);
|
||||
|
||||
expect(result1).toEqual(result2);
|
||||
});
|
||||
|
||||
it('should return different results for different field types', () => {
|
||||
const textResult = getDefaultFormFieldSettings(FieldMetadataType.TEXT);
|
||||
const numberResult = getDefaultFormFieldSettings(
|
||||
FieldMetadataType.NUMBER,
|
||||
);
|
||||
|
||||
expect(textResult).not.toEqual(numberResult);
|
||||
expect(textResult.name).not.toBe(numberResult.name);
|
||||
expect(textResult.label).not.toBe(numberResult.label);
|
||||
expect(textResult.placeholder).not.toBe(numberResult.placeholder);
|
||||
});
|
||||
it('should generate unique UUID for each call', () => {
|
||||
getDefaultFormFieldSettings(FieldMetadataType.TEXT);
|
||||
getDefaultFormFieldSettings(FieldMetadataType.NUMBER);
|
||||
expect(v4).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
describe('type safety', () => {
|
||||
it('should accept all valid WorkflowFormFieldType values', () => {
|
||||
const validTypes: WorkflowFormFieldType[] = [
|
||||
FieldMetadataType.TEXT,
|
||||
FieldMetadataType.NUMBER,
|
||||
FieldMetadataType.DATE,
|
||||
'RECORD',
|
||||
];
|
||||
it('should return object with required properties for TEXT type', () => {
|
||||
const result = getDefaultFormFieldSettings(FieldMetadataType.TEXT);
|
||||
expect(result).toHaveProperty('id');
|
||||
expect(result).toHaveProperty('name');
|
||||
expect(result).toHaveProperty('label');
|
||||
expect(result).toHaveProperty('placeholder');
|
||||
expect(result).not.toHaveProperty('settings');
|
||||
});
|
||||
|
||||
validTypes.forEach((type) => {
|
||||
expect(() => getDefaultFormFieldSettings(type)).not.toThrow();
|
||||
});
|
||||
});
|
||||
it('should return object with required properties for RECORD type', () => {
|
||||
const result = getDefaultFormFieldSettings('RECORD');
|
||||
expect(result).toHaveProperty('id');
|
||||
expect(result).toHaveProperty('name');
|
||||
expect(result).toHaveProperty('label');
|
||||
expect(result).toHaveProperty('placeholder');
|
||||
expect(result).toHaveProperty('settings');
|
||||
expect(result.settings).toHaveProperty('objectName');
|
||||
});
|
||||
|
||||
it('should have correct name, label, and placeholder for each field type', () => {
|
||||
const textResult = getDefaultFormFieldSettings(FieldMetadataType.TEXT);
|
||||
expect(textResult.name).toBe('text');
|
||||
expect(textResult.label).toBe('Text');
|
||||
expect(textResult.placeholder).toBe('Enter your text');
|
||||
|
||||
const numberResult = getDefaultFormFieldSettings(FieldMetadataType.NUMBER);
|
||||
expect(numberResult.name).toBe('number');
|
||||
expect(numberResult.label).toBe('Number');
|
||||
expect(numberResult.placeholder).toBe('1000');
|
||||
|
||||
const dateResult = getDefaultFormFieldSettings(FieldMetadataType.DATE);
|
||||
expect(dateResult.name).toBe('date');
|
||||
expect(dateResult.label).toBe('Date');
|
||||
expect(dateResult.placeholder).toBe('mm/dd/yyyy');
|
||||
|
||||
const recordResult = getDefaultFormFieldSettings('RECORD');
|
||||
expect(recordResult.name).toBe('record');
|
||||
expect(recordResult.label).toBe('Record');
|
||||
expect(recordResult.placeholder).toBe('Select a Company');
|
||||
});
|
||||
|
||||
it('should have correct settings object for RECORD type', () => {
|
||||
const result = getDefaultFormFieldSettings('RECORD');
|
||||
expect(result.settings).toEqual({ objectName: 'company' });
|
||||
expect(result.settings?.objectName).toBe('company');
|
||||
});
|
||||
|
||||
it('should return consistent results for the same field type', () => {
|
||||
const result1 = getDefaultFormFieldSettings(FieldMetadataType.TEXT);
|
||||
const result2 = getDefaultFormFieldSettings(FieldMetadataType.TEXT);
|
||||
expect(result1).toEqual(result2);
|
||||
});
|
||||
|
||||
it('should return different results for different field types', () => {
|
||||
const textResult = getDefaultFormFieldSettings(FieldMetadataType.TEXT);
|
||||
const numberResult = getDefaultFormFieldSettings(FieldMetadataType.NUMBER);
|
||||
expect(textResult).not.toEqual(numberResult);
|
||||
expect(textResult.name).not.toBe(numberResult.name);
|
||||
expect(textResult.label).not.toBe(numberResult.label);
|
||||
expect(textResult.placeholder).not.toBe(numberResult.placeholder);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { WorkflowActionType } from '@/workflow/types/Workflow';
|
||||
import { Theme } from '@emotion/react';
|
||||
import { COLOR, GRAY_SCALE } from 'twenty-ui/theme';
|
||||
import { getActionIconColorOrThrow } from '../getActionIconColorOrThrow';
|
||||
@ -17,300 +16,112 @@ const mockTheme: Theme = {
|
||||
} as Theme;
|
||||
|
||||
describe('getActionIconColorOrThrow', () => {
|
||||
describe('action types that return orange color', () => {
|
||||
it('should return orange color for CODE action type', () => {
|
||||
const result = getActionIconColorOrThrow({
|
||||
theme: mockTheme,
|
||||
actionType: 'CODE',
|
||||
});
|
||||
|
||||
expect(result).toBe(mockTheme.color.orange);
|
||||
});
|
||||
|
||||
it('should return orange color for HTTP_REQUEST action type', () => {
|
||||
const result = getActionIconColorOrThrow({
|
||||
theme: mockTheme,
|
||||
actionType: 'HTTP_REQUEST',
|
||||
});
|
||||
|
||||
expect(result).toBe(mockTheme.color.orange);
|
||||
});
|
||||
it('should return orange color for CODE action type', () => {
|
||||
expect(
|
||||
getActionIconColorOrThrow({ theme: mockTheme, actionType: 'CODE' }),
|
||||
).toBe(mockTheme.color.orange);
|
||||
});
|
||||
|
||||
describe('action types that return tertiary font color', () => {
|
||||
const recordActionTypes: WorkflowActionType[] = [
|
||||
'CREATE_RECORD',
|
||||
'UPDATE_RECORD',
|
||||
'DELETE_RECORD',
|
||||
'FIND_RECORDS',
|
||||
'FORM',
|
||||
];
|
||||
|
||||
recordActionTypes.forEach((actionType) => {
|
||||
it(`should return tertiary font color for ${actionType} action type`, () => {
|
||||
const result = getActionIconColorOrThrow({
|
||||
theme: mockTheme,
|
||||
actionType,
|
||||
});
|
||||
|
||||
expect(result).toBe(mockTheme.font.color.tertiary);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('action types that return blue color', () => {
|
||||
it('should return blue color for SEND_EMAIL action type', () => {
|
||||
const result = getActionIconColorOrThrow({
|
||||
theme: mockTheme,
|
||||
actionType: 'SEND_EMAIL',
|
||||
});
|
||||
|
||||
expect(result).toBe(mockTheme.color.blue);
|
||||
});
|
||||
});
|
||||
|
||||
describe('action types that return pink color', () => {
|
||||
it('should return pink color for AI_AGENT action type', () => {
|
||||
const result = getActionIconColorOrThrow({
|
||||
theme: mockTheme,
|
||||
actionType: 'AI_AGENT',
|
||||
});
|
||||
|
||||
expect(result).toBe(mockTheme.color.pink);
|
||||
});
|
||||
});
|
||||
|
||||
describe('FILTER action type', () => {
|
||||
it('should throw an error for FILTER action type', () => {
|
||||
expect(() => {
|
||||
getActionIconColorOrThrow({
|
||||
theme: mockTheme,
|
||||
actionType: 'FILTER',
|
||||
});
|
||||
}).toThrow("The Filter action isn't meant to be displayed as a node.");
|
||||
});
|
||||
});
|
||||
|
||||
describe('theme object handling', () => {
|
||||
it('should use the provided theme colors correctly', () => {
|
||||
const customTheme: Theme = {
|
||||
color: {
|
||||
orange: COLOR.red,
|
||||
blue: COLOR.purple,
|
||||
pink: COLOR.turquoise,
|
||||
},
|
||||
font: {
|
||||
color: {
|
||||
tertiary: GRAY_SCALE.gray50,
|
||||
},
|
||||
},
|
||||
} as Theme;
|
||||
|
||||
expect(
|
||||
getActionIconColorOrThrow({
|
||||
theme: customTheme,
|
||||
actionType: 'CODE',
|
||||
}),
|
||||
).toBe(COLOR.red);
|
||||
|
||||
expect(
|
||||
getActionIconColorOrThrow({
|
||||
theme: customTheme,
|
||||
actionType: 'SEND_EMAIL',
|
||||
}),
|
||||
).toBe(COLOR.purple);
|
||||
|
||||
expect(
|
||||
getActionIconColorOrThrow({
|
||||
theme: customTheme,
|
||||
actionType: 'AI_AGENT',
|
||||
}),
|
||||
).toBe(COLOR.turquoise);
|
||||
|
||||
expect(
|
||||
getActionIconColorOrThrow({
|
||||
theme: customTheme,
|
||||
actionType: 'CREATE_RECORD',
|
||||
}),
|
||||
).toBe(GRAY_SCALE.gray50);
|
||||
});
|
||||
});
|
||||
|
||||
describe('type safety and exhaustive checking', () => {
|
||||
it('should handle all valid action types without throwing unreachable errors', () => {
|
||||
const validActionTypes: WorkflowActionType[] = [
|
||||
'CODE',
|
||||
'HTTP_REQUEST',
|
||||
'CREATE_RECORD',
|
||||
'UPDATE_RECORD',
|
||||
'DELETE_RECORD',
|
||||
'FIND_RECORDS',
|
||||
'FORM',
|
||||
'SEND_EMAIL',
|
||||
'AI_AGENT',
|
||||
];
|
||||
|
||||
validActionTypes.forEach((actionType) => {
|
||||
expect(() => {
|
||||
getActionIconColorOrThrow({
|
||||
theme: mockTheme,
|
||||
actionType,
|
||||
});
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return consistent color values for the same action type', () => {
|
||||
const actionType: WorkflowActionType = 'CODE';
|
||||
const result1 = getActionIconColorOrThrow({
|
||||
theme: mockTheme,
|
||||
actionType,
|
||||
});
|
||||
const result2 = getActionIconColorOrThrow({
|
||||
theme: mockTheme,
|
||||
actionType,
|
||||
});
|
||||
|
||||
expect(result1).toBe(result2);
|
||||
expect(result1).toBe(mockTheme.color.orange);
|
||||
});
|
||||
});
|
||||
|
||||
describe('color grouping logic', () => {
|
||||
it('should group CODE and HTTP_REQUEST actions with orange color', () => {
|
||||
const orangeActions: WorkflowActionType[] = ['CODE', 'HTTP_REQUEST'];
|
||||
|
||||
orangeActions.forEach((actionType) => {
|
||||
const result = getActionIconColorOrThrow({
|
||||
theme: mockTheme,
|
||||
actionType,
|
||||
});
|
||||
expect(result).toBe(mockTheme.color.orange);
|
||||
});
|
||||
});
|
||||
|
||||
it('should group record-related actions with tertiary font color', () => {
|
||||
const recordActions: WorkflowActionType[] = [
|
||||
'CREATE_RECORD',
|
||||
'UPDATE_RECORD',
|
||||
'DELETE_RECORD',
|
||||
'FIND_RECORDS',
|
||||
'FORM',
|
||||
];
|
||||
|
||||
recordActions.forEach((actionType) => {
|
||||
const result = getActionIconColorOrThrow({
|
||||
theme: mockTheme,
|
||||
actionType,
|
||||
});
|
||||
expect(result).toBe(mockTheme.font.color.tertiary);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have unique colors for different action categories', () => {
|
||||
const orangeResult = getActionIconColorOrThrow({
|
||||
theme: mockTheme,
|
||||
actionType: 'CODE',
|
||||
});
|
||||
|
||||
const tertiaryResult = getActionIconColorOrThrow({
|
||||
it('should return tertiary font color for CREATE_RECORD action type', () => {
|
||||
expect(
|
||||
getActionIconColorOrThrow({
|
||||
theme: mockTheme,
|
||||
actionType: 'CREATE_RECORD',
|
||||
});
|
||||
|
||||
const blueResult = getActionIconColorOrThrow({
|
||||
theme: mockTheme,
|
||||
actionType: 'SEND_EMAIL',
|
||||
});
|
||||
|
||||
const pinkResult = getActionIconColorOrThrow({
|
||||
theme: mockTheme,
|
||||
actionType: 'AI_AGENT',
|
||||
});
|
||||
|
||||
const colors = [orangeResult, tertiaryResult, blueResult, pinkResult];
|
||||
const uniqueColors = new Set(colors);
|
||||
expect(uniqueColors.size).toBe(4);
|
||||
});
|
||||
}),
|
||||
).toBe(mockTheme.font.color.tertiary);
|
||||
});
|
||||
|
||||
describe('error handling edge cases', () => {
|
||||
it('should handle theme object with missing properties gracefully', () => {
|
||||
const incompleteTheme: Theme = {
|
||||
it('should return blue color for SEND_EMAIL action type', () => {
|
||||
expect(
|
||||
getActionIconColorOrThrow({ theme: mockTheme, actionType: 'SEND_EMAIL' }),
|
||||
).toBe(mockTheme.color.blue);
|
||||
});
|
||||
|
||||
it('should return pink color for AI_AGENT action type', () => {
|
||||
expect(
|
||||
getActionIconColorOrThrow({ theme: mockTheme, actionType: 'AI_AGENT' }),
|
||||
).toBe(mockTheme.color.pink);
|
||||
});
|
||||
|
||||
it('should throw an error for FILTER action type', () => {
|
||||
expect(() => {
|
||||
getActionIconColorOrThrow({ theme: mockTheme, actionType: 'FILTER' });
|
||||
}).toThrow("The Filter action isn't meant to be displayed as a node.");
|
||||
});
|
||||
|
||||
it('should use the provided theme colors correctly', () => {
|
||||
const customTheme: Theme = {
|
||||
color: {
|
||||
orange: COLOR.red,
|
||||
blue: COLOR.purple,
|
||||
pink: COLOR.turquoise,
|
||||
},
|
||||
font: {
|
||||
color: {
|
||||
orange: COLOR.orange,
|
||||
tertiary: GRAY_SCALE.gray50,
|
||||
},
|
||||
font: {
|
||||
color: {
|
||||
tertiary: GRAY_SCALE.gray40,
|
||||
},
|
||||
},
|
||||
} as Theme;
|
||||
},
|
||||
} as Theme;
|
||||
|
||||
expect(
|
||||
getActionIconColorOrThrow({
|
||||
theme: incompleteTheme,
|
||||
actionType: 'CODE',
|
||||
}),
|
||||
).toBe(COLOR.orange);
|
||||
expect(
|
||||
getActionIconColorOrThrow({ theme: customTheme, actionType: 'CODE' }),
|
||||
).toBe(COLOR.red);
|
||||
expect(
|
||||
getActionIconColorOrThrow({
|
||||
theme: customTheme,
|
||||
actionType: 'SEND_EMAIL',
|
||||
}),
|
||||
).toBe(COLOR.purple);
|
||||
expect(
|
||||
getActionIconColorOrThrow({ theme: customTheme, actionType: 'AI_AGENT' }),
|
||||
).toBe(COLOR.turquoise);
|
||||
expect(
|
||||
getActionIconColorOrThrow({
|
||||
theme: customTheme,
|
||||
actionType: 'CREATE_RECORD',
|
||||
}),
|
||||
).toBe(GRAY_SCALE.gray50);
|
||||
});
|
||||
|
||||
expect(
|
||||
getActionIconColorOrThrow({
|
||||
theme: incompleteTheme,
|
||||
actionType: 'CREATE_RECORD',
|
||||
}),
|
||||
).toBe(GRAY_SCALE.gray40);
|
||||
});
|
||||
|
||||
it('should return undefined when blue color is missing for SEND_EMAIL action', () => {
|
||||
const themeWithoutBlue: Theme = {
|
||||
it('should return undefined when blue color is missing for SEND_EMAIL action', () => {
|
||||
const themeWithoutBlue: Theme = {
|
||||
color: {
|
||||
orange: COLOR.orange,
|
||||
pink: COLOR.pink,
|
||||
},
|
||||
font: {
|
||||
color: {
|
||||
orange: COLOR.orange,
|
||||
pink: COLOR.pink,
|
||||
tertiary: GRAY_SCALE.gray40,
|
||||
},
|
||||
font: {
|
||||
color: {
|
||||
tertiary: GRAY_SCALE.gray40,
|
||||
},
|
||||
},
|
||||
} as Theme;
|
||||
},
|
||||
} as Theme;
|
||||
|
||||
const result = getActionIconColorOrThrow({
|
||||
expect(
|
||||
getActionIconColorOrThrow({
|
||||
theme: themeWithoutBlue,
|
||||
actionType: 'SEND_EMAIL',
|
||||
}),
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should handle null theme gracefully', () => {
|
||||
expect(() => {
|
||||
getActionIconColorOrThrow({
|
||||
theme: null as unknown as Theme,
|
||||
actionType: 'CODE',
|
||||
});
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
it('should return the same color for the same action type', () => {
|
||||
const result1 = getActionIconColorOrThrow({
|
||||
theme: mockTheme,
|
||||
actionType: 'CODE',
|
||||
});
|
||||
|
||||
it('should return undefined when pink color is missing for AI_AGENT action', () => {
|
||||
const themeWithoutPink: Theme = {
|
||||
color: {
|
||||
orange: COLOR.orange,
|
||||
blue: COLOR.blue,
|
||||
},
|
||||
font: {
|
||||
color: {
|
||||
tertiary: GRAY_SCALE.gray40,
|
||||
},
|
||||
},
|
||||
} as Theme;
|
||||
|
||||
const result = getActionIconColorOrThrow({
|
||||
theme: themeWithoutPink,
|
||||
actionType: 'AI_AGENT',
|
||||
});
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should handle null or undefined theme gracefully', () => {
|
||||
expect(() => {
|
||||
getActionIconColorOrThrow({
|
||||
theme: null as unknown as Theme,
|
||||
actionType: 'CODE',
|
||||
});
|
||||
}).toThrow();
|
||||
const result2 = getActionIconColorOrThrow({
|
||||
theme: mockTheme,
|
||||
actionType: 'CODE',
|
||||
});
|
||||
expect(result1).toBe(result2);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user