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,
restLink,
httpLink,
].filter(isDefined) as ApolloLink[],
].filter(isDefined),
);
};

View File

@ -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(),

View File

@ -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);
});
});
});
});

View File

@ -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);
});
});

View File

@ -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);
});
});