diff --git a/packages/devextreme/js/__internal/grids/grid_core/ai_assistant/__tests__/utils.test.ts b/packages/devextreme/js/__internal/grids/grid_core/ai_assistant/__tests__/utils.test.ts index 50c37cb52e0b..7b59661006b3 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/ai_assistant/__tests__/utils.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/ai_assistant/__tests__/utils.test.ts @@ -6,6 +6,8 @@ import type { Message } from '@js/ui/chat'; import { AI_ASSISTANT_AUTHOR_ID, MessageStatus } from '../const'; import { getMessageStatus, + hasAbortedCommands, + hasCommandErrors, isAIMessage, isChatOptions, isEnabledOption, @@ -119,6 +121,61 @@ describe('isChatOptions', () => { }); }); +describe('hasCommandErrors', () => { + it('should return true when commands contain failure status', () => { + const commands = [ + { status: 'success' as const, message: 'OK' }, + { status: 'failure' as const, message: 'Failed' }, + ]; + + expect(hasCommandErrors(commands)).toBe(true); + }); + + it('should return false when all commands are successful', () => { + const commands = [ + { status: 'success' as const, message: 'OK' }, + ]; + + expect(hasCommandErrors(commands)).toBe(false); + }); + + it('should return false when commands is undefined', () => { + expect(hasCommandErrors(undefined)).toBe(false); + }); + + it('should return false when commands contain only aborted status', () => { + const commands = [ + { status: 'aborted' as const, message: 'Aborted' }, + ]; + + expect(hasCommandErrors(commands)).toBe(false); + }); +}); + +describe('hasAbortedCommands', () => { + it('should return true when commands contain aborted status', () => { + const commands = [ + { status: 'success' as const, message: 'OK' }, + { status: 'aborted' as const, message: 'Aborted' }, + ]; + + expect(hasAbortedCommands(commands)).toBe(true); + }); + + it('should return false when no commands are aborted', () => { + const commands = [ + { status: 'success' as const, message: 'OK' }, + { status: 'failure' as const, message: 'Failed' }, + ]; + + expect(hasAbortedCommands(commands)).toBe(false); + }); + + it('should return false when commands is undefined', () => { + expect(hasAbortedCommands(undefined)).toBe(false); + }); +}); + describe('getMessageStatus', () => { it('should return Success when all commands are successful', () => { const commands = [ diff --git a/packages/devextreme/js/__internal/grids/grid_core/ai_assistant/utils.ts b/packages/devextreme/js/__internal/grids/grid_core/ai_assistant/utils.ts index 145fc17a28ee..ccf7c7bf5a3d 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/ai_assistant/utils.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/ai_assistant/utils.ts @@ -1,9 +1,8 @@ import { isObject } from '@js/core/utils/type'; import type { Message } from '@js/ui/chat'; -import { hasAbortedCommands, hasCommandErrors } from '../ai_chat/utils'; import { AI_ASSISTANT_AUTHOR_ID, MessageStatus } from './const'; -import type { AIMessage, CommandResults } from './types'; +import type { AIMessage, CommandResult } from './types'; export const isAIMessage = ( message: Message, @@ -21,7 +20,15 @@ export const isPopupOptions = (optionName: string, value: unknown): boolean => o export const isChatOptions = (optionName: string, value: unknown): boolean => optionName.startsWith('aiAssistant.chat') || (optionName === 'aiAssistant' && isObject(value) && 'chat' in value); -export const getMessageStatus = (commands: CommandResults): MessageStatus => { +export const hasCommandErrors = ( + commands: CommandResult[] | undefined, +): boolean => !!commands?.some(({ status }) => status === 'failure'); + +export const hasAbortedCommands = ( + commands: CommandResult[] | undefined, +): boolean => !!commands?.some(({ status }) => status === 'aborted'); + +export const getMessageStatus = (commands: CommandResult[]): MessageStatus => { if (hasCommandErrors(commands) || hasAbortedCommands(commands)) { return MessageStatus.Failure; } diff --git a/packages/devextreme/js/__internal/grids/grid_core/ai_chat/ai_chat.test.ts b/packages/devextreme/js/__internal/grids/grid_core/ai_chat/ai_chat.test.ts index b7dc7d7a67d7..b5088a47a6e8 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/ai_chat/ai_chat.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/ai_chat/ai_chat.test.ts @@ -394,26 +394,6 @@ describe('AIChat', () => { expect(icons[2].textContent).toBe(ABORTED_ITEM_EMOJI); }); - it('should render error icon when commands contain errors', () => { - createAIChat(); - triggerContentTemplate(); - - const chatConfig = getChatConfig(); - const container = document.createElement('div'); - - renderMessageTemplate(chatConfig, { - author: { id: AI_ASSISTANT_AUTHOR_ID, name: 'AI Assistant' }, - text: 'Mixed', - status: 'success', - commands: [ - { status: 'success', message: 'OK' }, - { status: 'failure', message: 'Failed' }, - ], - }, container); - - expect(container.querySelector(`.${CLASSES.messageIcon}`)?.classList.contains('dx-icon-errorcircle')).toBe(true); - }); - it('should not render regenerate button when all commands succeed', () => { const onRegenerate = jest.fn(); createAIChat({ onRegenerate }); @@ -432,30 +412,6 @@ describe('AIChat', () => { expect(container.querySelector(`.${CLASSES.messageRegenerateButton}`)).toBeNull(); }); - it('should render regenerate button when commands contain errors', () => { - const onRegenerate = jest.fn(); - createAIChat({ onRegenerate }); - triggerContentTemplate(); - - const chatConfig = getChatConfig(); - const container = document.createElement('div'); - - renderMessageTemplate(chatConfig, { - author: { id: AI_ASSISTANT_AUTHOR_ID, name: 'AI Assistant' }, - text: 'Mixed results', - status: 'success', - commands: [ - { status: 'success', message: 'OK' }, - { status: 'failure', message: 'Failed' }, - ], - }, container); - - const regenerateButton = container.querySelector(`.${CLASSES.messageRegenerateButton}`); - - expect(regenerateButton).not.toBeNull(); - expect(regenerateButton?.classList.contains(`dx-icon-${REGENERATE_ICON}`)).toBe(true); - }); - it('should not render command list when commands array is empty', () => { createAIChat(); triggerContentTemplate(); @@ -632,50 +588,6 @@ describe('AIChat', () => { expect(icons).toHaveLength(1); expect(icons[0].textContent).toBe(ABORTED_ITEM_EMOJI); }); - - it('should render error icon when commands contain aborted items', () => { - createAIChat(); - triggerContentTemplate(); - - const chatConfig = getChatConfig(); - const container = document.createElement('div'); - - renderMessageTemplate(chatConfig, { - author: { id: AI_ASSISTANT_AUTHOR_ID, name: 'AI Assistant' }, - text: 'Aborted', - status: 'success', - commands: [ - { status: 'success', message: 'OK' }, - { status: 'aborted', message: 'Aborted' }, - ], - }, container); - - expect(container.querySelector(`.${CLASSES.messageIcon}`)?.classList.contains('dx-icon-errorcircle')).toBe(true); - }); - - it('should render regenerate button when commands contain aborted items', () => { - const onRegenerate = jest.fn(); - createAIChat({ onRegenerate }); - triggerContentTemplate(); - - const chatConfig = getChatConfig(); - const container = document.createElement('div'); - - renderMessageTemplate(chatConfig, { - author: { id: AI_ASSISTANT_AUTHOR_ID, name: 'AI Assistant' }, - text: 'Aborted results', - status: 'success', - commands: [ - { status: 'success', message: 'OK' }, - { status: 'aborted', message: 'Aborted' }, - ], - }, container); - - const regenerateButton = container.querySelector(`.${CLASSES.messageRegenerateButton}`); - - expect(regenerateButton).not.toBeNull(); - expect(regenerateButton?.classList.contains(`dx-icon-${REGENERATE_ICON}`)).toBe(true); - }); }); describe('general', () => { diff --git a/packages/devextreme/js/__internal/grids/grid_core/ai_chat/utils.test.ts b/packages/devextreme/js/__internal/grids/grid_core/ai_chat/utils.test.ts index 4c14fec23a38..0ac8a2c2a6dd 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/ai_chat/utils.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/ai_chat/utils.test.ts @@ -10,67 +10,10 @@ import { import { getCommandItemStyle, getMessageIconName, - hasAbortedCommands, - hasCommandErrors, needToRenderCommandList, needToShowRegenerateButton, } from './utils'; -describe('hasCommandErrors', () => { - it('should return true when commands contain failure status', () => { - const commands = [ - { status: 'success' as const, message: 'OK' }, - { status: 'failure' as const, message: 'Failed' }, - ]; - - expect(hasCommandErrors(commands)).toBe(true); - }); - - it('should return false when all commands are successful', () => { - const commands = [ - { status: 'success' as const, message: 'OK' }, - ]; - - expect(hasCommandErrors(commands)).toBe(false); - }); - - it('should return false when commands is undefined', () => { - expect(hasCommandErrors(undefined)).toBe(false); - }); - - it('should return false when commands contain only aborted status', () => { - const commands = [ - { status: 'aborted' as const, message: 'Aborted' }, - ]; - - expect(hasCommandErrors(commands)).toBe(false); - }); -}); - -describe('hasAbortedCommands', () => { - it('should return true when commands contain aborted status', () => { - const commands = [ - { status: 'success' as const, message: 'OK' }, - { status: 'aborted' as const, message: 'Aborted' }, - ]; - - expect(hasAbortedCommands(commands)).toBe(true); - }); - - it('should return false when no commands are aborted', () => { - const commands = [ - { status: 'success' as const, message: 'OK' }, - { status: 'failure' as const, message: 'Failed' }, - ]; - - expect(hasAbortedCommands(commands)).toBe(false); - }); - - it('should return false when commands is undefined', () => { - expect(hasAbortedCommands(undefined)).toBe(false); - }); -}); - describe('getMessageIconName', () => { it('should return errorcircle when message status is failure', () => { const message = { status: MessageStatus.Failure } as Message; @@ -78,24 +21,6 @@ describe('getMessageIconName', () => { expect(getMessageIconName(message)).toBe('errorcircle'); }); - it('should return errorcircle when commands contain errors', () => { - const message = { - status: MessageStatus.Success, - commands: [{ status: 'failure', message: 'Failed' }], - } as Message; - - expect(getMessageIconName(message)).toBe('errorcircle'); - }); - - it('should return errorcircle when commands contain aborted items', () => { - const message = { - status: MessageStatus.Success, - commands: [{ status: 'aborted', message: 'Aborted' }], - } as Message; - - expect(getMessageIconName(message)).toBe('errorcircle'); - }); - it('should return checkmarkcirclefilled when message status is success', () => { const message = { status: MessageStatus.Success } as Message; @@ -110,31 +35,22 @@ describe('getMessageIconName', () => { }); describe('needToShowRegenerateButton', () => { - it('should return true when message status is failure', () => { + it('should return true when message status is failure and has no commands', () => { const message = { status: MessageStatus.Failure } as Message; expect(needToShowRegenerateButton(message)).toBe(true); }); - it('should return true when commands contain errors', () => { + it('should return false when message status is failure and has commands', () => { const message = { - status: MessageStatus.Success, + status: MessageStatus.Failure, commands: [{ status: 'failure', message: 'Failed' }], } as Message; - expect(needToShowRegenerateButton(message)).toBe(true); - }); - - it('should return true when commands contain aborted items', () => { - const message = { - status: MessageStatus.Success, - commands: [{ status: 'aborted', message: 'Aborted' }], - } as Message; - - expect(needToShowRegenerateButton(message)).toBe(true); + expect(needToShowRegenerateButton(message)).toBe(false); }); - it('should return false when message is successful without errors', () => { + it('should return false when message is successful', () => { const message = { status: MessageStatus.Success, commands: [{ status: 'success', message: 'OK' }], @@ -174,38 +90,35 @@ describe('getCommandItemStyle', () => { }); describe('needToRenderCommandList', () => { - it('should return true when message status is success', () => { - const message = { status: MessageStatus.Success } as Message; - - expect(needToRenderCommandList(message)).toBe(true); - }); - - it('should return true when commands contain errors', () => { + it('should return true when message has commands', () => { const message = { - status: MessageStatus.Failure, - commands: [{ status: 'failure', message: 'Failed' }], + status: MessageStatus.Success, + commands: [{ status: 'success', message: 'OK' }], } as Message; expect(needToRenderCommandList(message)).toBe(true); }); - it('should return true when commands contain aborted items', () => { + it('should return true when message has failed commands', () => { const message = { status: MessageStatus.Failure, - commands: [{ status: 'aborted', message: 'Aborted' }], + commands: [{ status: 'failure', message: 'Failed' }], } as Message; expect(needToRenderCommandList(message)).toBe(true); }); - it('should return false when message is pending without commands', () => { - const message = { status: MessageStatus.Pending } as Message; + it('should return false when message has no commands', () => { + const message = { status: MessageStatus.Success } as Message; expect(needToRenderCommandList(message)).toBe(false); }); - it('should return false when message is failure without commands', () => { - const message = { status: MessageStatus.Failure } as Message; + it('should return false when commands is empty array', () => { + const message = { + status: MessageStatus.Success, + commands: [], + } as unknown as Message; expect(needToRenderCommandList(message)).toBe(false); }); diff --git a/packages/devextreme/js/__internal/grids/grid_core/ai_chat/utils.ts b/packages/devextreme/js/__internal/grids/grid_core/ai_chat/utils.ts index 824a142efe13..548b3fc4c663 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/ai_chat/utils.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/ai_chat/utils.ts @@ -5,7 +5,6 @@ import type { CommandStatus } from '../ai_assistant/types'; import { ABORTED_ITEM_EMOJI, CLASSES, ERROR_ITEM_EMOJI, SUCCESS_ITEM_EMOJI, } from './const'; -import type { CommandResults } from './types'; export const getMessageStateClass = (status: MessageStatus): string => { switch (status) { @@ -19,26 +18,16 @@ export const getMessageStateClass = (status: MessageStatus): string => { } }; -export const hasCommandErrors = ( - commands: CommandResults | undefined, -): boolean => !!commands?.some(({ status }) => status === MessageStatus.Failure); - -export const hasAbortedCommands = ( - commands: CommandResults | undefined, -): boolean => !!commands?.some(({ status }) => status === 'aborted'); - export const getMessageIconName = (message: Message): string => { - if (message.status === MessageStatus.Failure - || hasCommandErrors(message.commands) - || hasAbortedCommands(message.commands)) { - return 'errorcircle'; - } - - if (message.status === MessageStatus.Success) { - return 'checkmarkcirclefilled'; + switch (message.status) { + case MessageStatus.Failure: + return 'errorcircle'; + case MessageStatus.Success: + return 'checkmarkcirclefilled'; + case MessageStatus.Pending: + default: + return 'sparkle'; } - - return 'sparkle'; }; export const findMessageById = ( @@ -49,11 +38,7 @@ export const findMessageById = ( export const needToShowRegenerateButton = (message: Message): boolean => { const isError = message.status === MessageStatus.Failure; - if (isError) { - return true; - } - - return hasCommandErrors(message.commands) || hasAbortedCommands(message.commands); + return isError && !message.commands?.length; }; export const getCommandItemStyle = (status: CommandStatus): { @@ -73,6 +58,4 @@ export const getCommandItemStyle = (status: CommandStatus): { export const needToRenderCommandList = ( message: Message, -): boolean => message.status === MessageStatus.Success - || hasCommandErrors(message.commands) - || hasAbortedCommands(message.commands); +): boolean => !!message.commands?.length;