diff --git a/packages/twenty-front/src/modules/apollo/services/apollo.factory.ts b/packages/twenty-front/src/modules/apollo/services/apollo.factory.ts index 3bca7c797..2a76e445a 100644 --- a/packages/twenty-front/src/modules/apollo/services/apollo.factory.ts +++ b/packages/twenty-front/src/modules/apollo/services/apollo.factory.ts @@ -230,7 +230,7 @@ export class ApolloFactory implements ApolloManager { retryLink, restLink, httpLink, - ].filter(isDefined) as ApolloLink[], + ].filter(isDefined), ); }; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/ai-agent-action/hooks/useAgentChat.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/ai-agent-action/hooks/useAgentChat.ts index d874527a5..8a14bc208 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/ai-agent-action/hooks/useAgentChat.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/ai-agent-action/hooks/useAgentChat.ts @@ -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(), diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/ai-agent-action/utils/__tests__/getFieldIcon.test.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/ai-agent-action/utils/__tests__/getFieldIcon.test.ts index e76194b5d..099912b74 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/ai-agent-action/utils/__tests__/getFieldIcon.test.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/ai-agent-action/utils/__tests__/getFieldIcon.test.ts @@ -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); - }); }); }); }); diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/form-action/utils/__tests__/getDefaultFormFieldSettings.test.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/form-action/utils/__tests__/getDefaultFormFieldSettings.test.ts index 93041cbc3..8bbe03a0e 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/form-action/utils/__tests__/getDefaultFormFieldSettings.test.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/form-action/utils/__tests__/getDefaultFormFieldSettings.test.ts @@ -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); }); }); diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/__tests__/getActionIconColorOrThrow.test.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/__tests__/getActionIconColorOrThrow.test.ts index 81b1a9b6d..9953d393b 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/__tests__/getActionIconColorOrThrow.test.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/__tests__/getActionIconColorOrThrow.test.ts @@ -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); }); });