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:
Abdul Rahman
2025-07-09 00:18:22 +05:30
committed by GitHub
parent 9eaa8ad517
commit c8ec44eeaf
5 changed files with 218 additions and 683 deletions

View File

@ -230,7 +230,7 @@ export class ApolloFactory<TCacheShape> implements ApolloManager<TCacheShape> {
retryLink, retryLink,
restLink, restLink,
httpLink, httpLink,
].filter(isDefined) as ApolloLink[], ].filter(isDefined),
); );
}; };

View File

@ -1,12 +1,13 @@
import { InputHotkeyScope } from '@/ui/input/types/InputHotkeyScope'; import { InputHotkeyScope } from '@/ui/input/types/InputHotkeyScope';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2'; import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
import { useCallback, useState } from 'react'; import { useState } from 'react';
import { useRecoilState } from 'recoil'; import { useRecoilState } from 'recoil';
import { Key } from 'ts-key-enum'; import { Key } from 'ts-key-enum';
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement'; import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
import { useScrollWrapperElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperElement'; 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 { 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 { v4 } from 'uuid';
import { streamChatResponse } from '../api/streamChatResponse'; import { streamChatResponse } from '../api/streamChatResponse';
import { agentChatInputState } from '../states/agentChatInputState'; import { agentChatInputState } from '../states/agentChatInputState';
@ -36,22 +37,32 @@ export const useAgentChat = (agentId: string) => {
const { scrollWrapperHTMLElement } = useScrollWrapperElement(agentId); const { scrollWrapperHTMLElement } = useScrollWrapperElement(agentId);
const scrollToBottom = useCallback(() => { const scrollToBottom = () => {
scrollWrapperHTMLElement?.scroll({ scrollWrapperHTMLElement?.scroll({
top: scrollWrapperHTMLElement.scrollHeight, top: scrollWrapperHTMLElement.scrollHeight,
behavior: 'smooth', behavior: 'smooth',
}); });
}, [scrollWrapperHTMLElement]); };
const { data: { threads = [] } = {}, loading: threadsLoading } = const { data: { threads = [] } = {}, loading: threadsLoading } =
useAgentChatThreads(agentId); useAgentChatThreads(agentId);
const currentThreadId = threads[0]?.id; const currentThreadId = threads[0]?.id;
const { loading: messagesLoading, refetch: refetchMessages } = const {
useAgentChatMessages(currentThreadId); data: messagesData,
loading: messagesLoading,
refetch: refetchMessages,
} = useAgentChatMessages(currentThreadId);
const isLoading = messagesLoading || threadsLoading || isStreaming; const isLoading = messagesLoading || threadsLoading || isStreaming;
if (
agentChatMessages.length === 0 &&
isDefined(messagesData?.messages?.length)
) {
setAgentChatMessages(messagesData.messages);
}
const createOptimisticMessages = (content: string): AgentChatMessage[] => { const createOptimisticMessages = (content: string): AgentChatMessage[] => {
const optimisticUserMessage: OptimisticMessage = { const optimisticUserMessage: OptimisticMessage = {
id: v4(), id: v4(),

View File

@ -2,237 +2,43 @@ import { FieldMetadataType } from 'twenty-shared/types';
import { getFieldIcon } from '../getFieldIcon'; import { getFieldIcon } from '../getFieldIcon';
describe('getFieldIcon', () => { describe('getFieldIcon', () => {
const UNSUPPORTED_FIELD_TYPES = [ describe('supported 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', () => {
it('should return IconAbc for TEXT field type', () => { it('should return IconAbc for TEXT field type', () => {
const result = getFieldIcon(FieldMetadataType.TEXT); expect(getFieldIcon(FieldMetadataType.TEXT)).toBe('IconAbc');
expect(result).toBe('IconAbc');
}); });
it('should return IconText for NUMBER field type', () => { it('should return IconText for NUMBER field type', () => {
const result = getFieldIcon(FieldMetadataType.NUMBER); expect(getFieldIcon(FieldMetadataType.NUMBER)).toBe('IconText');
expect(result).toBe('IconText');
}); });
it('should return IconCheckbox for BOOLEAN field type', () => { it('should return IconCheckbox for BOOLEAN field type', () => {
const result = getFieldIcon(FieldMetadataType.BOOLEAN); expect(getFieldIcon(FieldMetadataType.BOOLEAN)).toBe('IconCheckbox');
expect(result).toBe('IconCheckbox');
}); });
it('should return IconCalendarEvent for DATE field type', () => { it('should return IconCalendarEvent for DATE field type', () => {
const result = getFieldIcon(FieldMetadataType.DATE); expect(getFieldIcon(FieldMetadataType.DATE)).toBe('IconCalendarEvent');
expect(result).toBe('IconCalendarEvent');
}); });
}); });
describe('basic InputSchemaPropertyType field types', () => { describe('unsupported and edge cases', () => {
it('should return IconQuestionMark for string type', () => { it('should return IconQuestionMark for an unsupported field type', () => {
const result = getFieldIcon('string'); expect(getFieldIcon('totally-unknown-type' as any)).toBe(
expect(result).toBe('IconQuestionMark'); 'IconQuestionMark',
);
}); });
it('should return IconQuestionMark for undefined', () => {
it('should return IconQuestionMark for number type', () => { expect(getFieldIcon(undefined)).toBe('IconQuestionMark');
const result = getFieldIcon('number');
expect(result).toBe('IconQuestionMark');
}); });
it('should return IconQuestionMark for null', () => {
it('should return IconQuestionMark for boolean type', () => { expect(getFieldIcon(null as any)).toBe('IconQuestionMark');
const result = getFieldIcon('boolean');
expect(result).toBe('IconQuestionMark');
}); });
it('should return IconQuestionMark for empty string', () => {
it('should return IconQuestionMark for object type', () => { expect(getFieldIcon('' as any)).toBe('IconQuestionMark');
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');
}); });
}); });
describe('other FieldMetadataType values', () => { describe('consistency', () => {
it('should return IconQuestionMark for DATE_TIME field type', () => { it('should return the same icon for the same field type', () => {
const result = getFieldIcon(FieldMetadataType.DATE_TIME); const result1 = getFieldIcon(FieldMetadataType.TEXT);
expect(result).toBe('IconQuestionMark'); const result2 = getFieldIcon(FieldMetadataType.TEXT);
});
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);
expect(result1).toBe(result2); 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);
});
}); });
}); });
}); });

View File

@ -1,6 +1,5 @@
import { FieldMetadataType } from 'twenty-shared/types'; import { FieldMetadataType } from 'twenty-shared/types';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { WorkflowFormFieldType } from '../../types/WorkflowFormFieldType';
import { getDefaultFormFieldSettings } from '../getDefaultFormFieldSettings'; import { getDefaultFormFieldSettings } from '../getDefaultFormFieldSettings';
jest.mock('uuid', () => ({ jest.mock('uuid', () => ({
@ -12,206 +11,114 @@ describe('getDefaultFormFieldSettings', () => {
jest.clearAllMocks(); jest.clearAllMocks();
}); });
describe('FieldMetadataType.TEXT', () => { it('should return correct default settings for TEXT field type', () => {
it('should return correct default settings for TEXT field type', () => { const result = getDefaultFormFieldSettings(FieldMetadataType.TEXT);
const result = getDefaultFormFieldSettings(FieldMetadataType.TEXT); expect(result).toEqual({
id: 'test-uuid-123',
expect(result).toEqual({ name: 'text',
id: 'test-uuid-123', label: 'Text',
name: 'text', placeholder: 'Enter your text',
label: 'Text',
placeholder: 'Enter your text',
});
}); });
}); });
describe('FieldMetadataType.NUMBER', () => { it('should return correct default settings for NUMBER field type', () => {
it('should return correct default settings for NUMBER field type', () => { const result = getDefaultFormFieldSettings(FieldMetadataType.NUMBER);
const result = getDefaultFormFieldSettings(FieldMetadataType.NUMBER); expect(result).toEqual({
id: 'test-uuid-123',
expect(result).toEqual({ name: 'number',
id: 'test-uuid-123', label: 'Number',
name: 'number', placeholder: '1000',
label: 'Number',
placeholder: '1000',
});
}); });
}); });
describe('FieldMetadataType.DATE', () => { it('should return correct default settings for DATE field type', () => {
it('should return correct default settings for DATE field type', () => { const result = getDefaultFormFieldSettings(FieldMetadataType.DATE);
const result = getDefaultFormFieldSettings(FieldMetadataType.DATE); expect(result).toEqual({
id: 'test-uuid-123',
expect(result).toEqual({ name: 'date',
id: 'test-uuid-123', label: 'Date',
name: 'date', placeholder: 'mm/dd/yyyy',
label: 'Date',
placeholder: 'mm/dd/yyyy',
});
}); });
}); });
describe('RECORD type', () => { it('should return correct default settings for RECORD field type', () => {
it('should return correct default settings for RECORD field type', () => { const result = getDefaultFormFieldSettings('RECORD');
const result = getDefaultFormFieldSettings('RECORD'); expect(result).toEqual({
id: 'test-uuid-123',
expect(result).toEqual({ name: 'record',
id: 'test-uuid-123', label: 'Record',
name: 'record', placeholder: 'Select a Company',
label: 'Record', settings: {
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({
objectName: 'company', 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 generate unique UUID for each call', () => {
it('should return consistent results for the same field type', () => { getDefaultFormFieldSettings(FieldMetadataType.TEXT);
const result1 = getDefaultFormFieldSettings(FieldMetadataType.TEXT); getDefaultFormFieldSettings(FieldMetadataType.NUMBER);
const result2 = getDefaultFormFieldSettings(FieldMetadataType.TEXT); expect(v4).toHaveBeenCalledTimes(2);
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);
});
}); });
describe('type safety', () => { it('should return object with required properties for TEXT type', () => {
it('should accept all valid WorkflowFormFieldType values', () => { const result = getDefaultFormFieldSettings(FieldMetadataType.TEXT);
const validTypes: WorkflowFormFieldType[] = [ expect(result).toHaveProperty('id');
FieldMetadataType.TEXT, expect(result).toHaveProperty('name');
FieldMetadataType.NUMBER, expect(result).toHaveProperty('label');
FieldMetadataType.DATE, expect(result).toHaveProperty('placeholder');
'RECORD', expect(result).not.toHaveProperty('settings');
]; });
validTypes.forEach((type) => { it('should return object with required properties for RECORD type', () => {
expect(() => getDefaultFormFieldSettings(type)).not.toThrow(); 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);
}); });
}); });

View File

@ -1,4 +1,3 @@
import { WorkflowActionType } from '@/workflow/types/Workflow';
import { Theme } from '@emotion/react'; import { Theme } from '@emotion/react';
import { COLOR, GRAY_SCALE } from 'twenty-ui/theme'; import { COLOR, GRAY_SCALE } from 'twenty-ui/theme';
import { getActionIconColorOrThrow } from '../getActionIconColorOrThrow'; import { getActionIconColorOrThrow } from '../getActionIconColorOrThrow';
@ -17,300 +16,112 @@ const mockTheme: Theme = {
} as Theme; } as Theme;
describe('getActionIconColorOrThrow', () => { describe('getActionIconColorOrThrow', () => {
describe('action types that return orange color', () => { it('should return orange color for CODE action type', () => {
it('should return orange color for CODE action type', () => { expect(
const result = getActionIconColorOrThrow({ getActionIconColorOrThrow({ theme: mockTheme, actionType: 'CODE' }),
theme: mockTheme, ).toBe(mockTheme.color.orange);
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);
});
}); });
describe('action types that return tertiary font color', () => { it('should return tertiary font color for CREATE_RECORD action type', () => {
const recordActionTypes: WorkflowActionType[] = [ expect(
'CREATE_RECORD', getActionIconColorOrThrow({
'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({
theme: mockTheme, theme: mockTheme,
actionType: 'CREATE_RECORD', actionType: 'CREATE_RECORD',
}); }),
).toBe(mockTheme.font.color.tertiary);
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);
});
}); });
describe('error handling edge cases', () => { it('should return blue color for SEND_EMAIL action type', () => {
it('should handle theme object with missing properties gracefully', () => { expect(
const incompleteTheme: Theme = { 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: { color: {
orange: COLOR.orange, tertiary: GRAY_SCALE.gray50,
}, },
font: { },
color: { } as Theme;
tertiary: GRAY_SCALE.gray40,
},
},
} as Theme;
expect( expect(
getActionIconColorOrThrow({ getActionIconColorOrThrow({ theme: customTheme, actionType: 'CODE' }),
theme: incompleteTheme, ).toBe(COLOR.red);
actionType: 'CODE', expect(
}), getActionIconColorOrThrow({
).toBe(COLOR.orange); 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( it('should return undefined when blue color is missing for SEND_EMAIL action', () => {
getActionIconColorOrThrow({ const themeWithoutBlue: Theme = {
theme: incompleteTheme, color: {
actionType: 'CREATE_RECORD', orange: COLOR.orange,
}), pink: COLOR.pink,
).toBe(GRAY_SCALE.gray40); },
}); font: {
it('should return undefined when blue color is missing for SEND_EMAIL action', () => {
const themeWithoutBlue: Theme = {
color: { color: {
orange: COLOR.orange, tertiary: GRAY_SCALE.gray40,
pink: COLOR.pink,
}, },
font: { },
color: { } as Theme;
tertiary: GRAY_SCALE.gray40,
},
},
} as Theme;
const result = getActionIconColorOrThrow({ expect(
getActionIconColorOrThrow({
theme: themeWithoutBlue, theme: themeWithoutBlue,
actionType: 'SEND_EMAIL', 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',
}); });
const result2 = getActionIconColorOrThrow({
it('should return undefined when pink color is missing for AI_AGENT action', () => { theme: mockTheme,
const themeWithoutPink: Theme = { actionType: 'CODE',
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();
}); });
expect(result1).toBe(result2);
}); });
}); });