From f50a6d99c469b97c6150616dea65364d6a3f2e4e Mon Sep 17 00:00:00 2001 From: iamsivin Date: Fri, 23 Aug 2024 14:16:10 +0530 Subject: [PATCH] chore: Adds spec for conversation action composable --- .../dashboard/commands/spec/fixtures.js | 186 +++++++++++++++ .../spec/useConversationHotKeys.spec.js | 221 ++++++++++++++++++ 2 files changed, 407 insertions(+) create mode 100644 app/javascript/dashboard/routes/dashboard/commands/spec/fixtures.js create mode 100644 app/javascript/dashboard/routes/dashboard/commands/spec/useConversationHotKeys.spec.js diff --git a/app/javascript/dashboard/routes/dashboard/commands/spec/fixtures.js b/app/javascript/dashboard/routes/dashboard/commands/spec/fixtures.js new file mode 100644 index 000000000..6fd16aeb9 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/commands/spec/fixtures.js @@ -0,0 +1,186 @@ +export const mockAssignableAgents = [ + { + id: 1, + account_id: 1, + availability_status: 'online', + auto_offline: true, + confirmed: true, + email: 'john@doe.com', + available_name: 'John Doe', + name: 'John Doe', + role: 'administrator', + thumbnail: '', + }, +]; + +export const mockCurrentChat = { + meta: { + sender: { + additional_attributes: {}, + availability_status: 'offline', + email: null, + id: 212, + name: 'Chatwoot', + phone_number: null, + identifier: null, + thumbnail: '', + custom_attributes: {}, + last_activity_at: 1723553344, + created_at: 1722588710, + }, + channel: 'Channel::WebWidget', + assignee: { + id: 1, + account_id: 1, + availability_status: 'online', + auto_offline: true, + confirmed: true, + email: 'john@doe.com', + available_name: 'John Doe', + name: 'John Doe', + role: 'administrator', + thumbnail: '', + }, + hmac_verified: false, + }, + id: 138, + messages: [ + { + id: 3348, + content: 'Hello, how can I assist you today?', + account_id: 1, + inbox_id: 1, + conversation_id: 138, + message_type: 1, + created_at: 1724398739, + updated_at: '2024-08-23T07:38:59.763Z', + private: false, + status: 'sent', + source_id: null, + content_type: 'text', + content_attributes: {}, + sender_type: 'User', + sender_id: 1, + external_source_ids: {}, + additional_attributes: {}, + processed_message_content: 'Hello, how can I assist you today?', + sentiment: {}, + conversation: { + assignee_id: 1, + unread_count: 0, + last_activity_at: 1724398739, + contact_inbox: { + source_id: '5e57317d-053b-4a72-8292-a25b9f29c401', + }, + }, + sender: { + id: 1, + name: 'John Doe', + available_name: 'John Doe', + avatar_url: '', + type: 'user', + availability_status: 'online', + thumbnail: '', + }, + }, + ], + account_id: 1, + uuid: '69dd6922-2f0c-4317-8796-bbeb3679cead', + additional_attributes: { + browser: { + device_name: 'Unknown', + browser_name: 'Chrome', + platform_name: 'macOS', + browser_version: '127.0.0.0', + platform_version: '10.15.7', + }, + referer: 'http://chatwoot.com/widget_tests?dark_mode=auto', + initiated_at: { + timestamp: 'Fri Aug 02 2024 15:21:18 GMT+0530 (India Standard Time)', + }, + browser_language: 'en', + }, + agent_last_seen_at: 1724400730, + assignee_last_seen_at: 1724400686, + can_reply: true, + contact_last_seen_at: 1723553351, + custom_attributes: {}, + inbox_id: 1, + labels: ['billing'], + muted: false, + snoozed_until: null, + status: 'open', + created_at: 1722592278, + timestamp: 1724398739, + first_reply_created_at: 1722592316, + unread_count: 0, + last_non_activity_message: {}, + last_activity_at: 1724398739, + priority: null, + waiting_since: 0, + sla_policy_id: 10, + applied_sla: { + id: 143, + sla_id: 10, + sla_status: 'missed', + created_at: 1722592279, + updated_at: 1722874214, + sla_description: '', + sla_name: 'Hacker SLA', + sla_first_response_time_threshold: 600, + sla_next_response_time_threshold: 240, + sla_only_during_business_hours: false, + sla_resolution_time_threshold: 259200, + }, + sla_events: [ + { + id: 270, + event_type: 'nrt', + meta: { + message_id: 2743, + }, + updated_at: 1722592819, + created_at: 1722592819, + }, + { + id: 275, + event_type: 'rt', + meta: {}, + updated_at: 1722852322, + created_at: 1722852322, + }, + ], + allMessagesLoaded: false, + dataFetched: true, +}; + +export const mockTeamsList = [ + { + id: 5, + name: 'design', + description: 'design team', + allow_auto_assign: true, + account_id: 1, + is_member: false, + }, +]; + +export const mockActiveLabels = [ + { + id: 16, + title: 'billing', + description: '', + color: '#D8EA19', + show_on_sidebar: true, + }, +]; + +export const mockInactiveLabels = [ + { + id: 2, + title: 'Feature Request', + description: '', + color: '#D8EA19', + show_on_sidebar: true, + }, +]; diff --git a/app/javascript/dashboard/routes/dashboard/commands/spec/useConversationHotKeys.spec.js b/app/javascript/dashboard/routes/dashboard/commands/spec/useConversationHotKeys.spec.js new file mode 100644 index 000000000..fb16d1dfc --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/commands/spec/useConversationHotKeys.spec.js @@ -0,0 +1,221 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { useConversationHotKeys } from '../useConversationHotKeys'; +import { useStore, useMapGetter } from 'dashboard/composables/store'; +import { useI18n } from 'dashboard/composables/useI18n'; +import { useRoute } from 'dashboard/composables/route'; +import { useConversationLabels } from 'dashboard/composables/useConversationLabels'; +import { useAI } from 'dashboard/composables/useAI'; +import { useAgentsList } from 'dashboard/composables/useAgentsList'; +import { REPLY_EDITOR_MODES } from 'dashboard/components/widgets/WootWriter/constants'; +import { + mockAssignableAgents, + mockCurrentChat, + mockTeamsList, + mockActiveLabels, + mockInactiveLabels, +} from './fixtures'; + +vi.mock('dashboard/composables/store'); +vi.mock('dashboard/composables/useI18n'); +vi.mock('dashboard/composables/route'); +vi.mock('dashboard/composables/useConversationLabels'); +vi.mock('dashboard/composables/useAI'); +vi.mock('dashboard/composables/useAgentsList'); + +describe('useConversationHotKeys', () => { + let store; + + beforeEach(() => { + store = { + dispatch: vi.fn(), + getters: { + getSelectedChat: mockCurrentChat, + 'draftMessages/getReplyEditorMode': REPLY_EDITOR_MODES.REPLY, + getContextMenuChatId: null, + 'teams/getTeams': mockTeamsList, + 'draftMessages/get': vi.fn(), + }, + }; + + useStore.mockReturnValue(store); + useMapGetter.mockImplementation(key => ({ + value: store.getters[key], + })); + + useI18n.mockReturnValue({ t: vi.fn(key => key) }); + useRoute.mockReturnValue({ name: 'inbox_conversation' }); + useConversationLabels.mockReturnValue({ + activeLabels: { value: mockActiveLabels }, + inactiveLabels: { value: mockInactiveLabels }, + addLabelToConversation: vi.fn(), + removeLabelFromConversation: vi.fn(), + }); + useAI.mockReturnValue({ isAIIntegrationEnabled: { value: true } }); + useAgentsList.mockReturnValue({ + agentsList: { value: [] }, + assignableAgents: { value: mockAssignableAgents }, + }); + }); + + it('should return the correct computed properties', () => { + const { + assignableAgents, + currentChat, + teamsList, + activeLabels, + draftMessage, + replyMode, + contextMenuChatId, + conversationHotKeys, + } = useConversationHotKeys(); + + expect(assignableAgents.value).toEqual(mockAssignableAgents); + expect(currentChat.value).toEqual(mockCurrentChat); + expect(teamsList.value).toEqual(mockTeamsList); + expect(activeLabels.value).toEqual(mockActiveLabels); + expect(draftMessage.value).toBeUndefined(); + expect(replyMode.value).toBe(REPLY_EDITOR_MODES.REPLY); + expect(contextMenuChatId.value).toBeNull(); + expect(conversationHotKeys.value).toBeDefined(); + }); + + it('should generate conversation hot keys', () => { + const { conversationHotKeys } = useConversationHotKeys(); + expect(conversationHotKeys.value.length).toBeGreaterThan(0); + }); + + it('should include AI assist actions when AI integration is enabled', () => { + const { conversationHotKeys } = useConversationHotKeys(); + const aiAssistAction = conversationHotKeys.value.find( + action => action.id === 'ai_assist' + ); + expect(aiAssistAction).toBeDefined(); + }); + + it('should not include AI assist actions when AI integration is disabled', () => { + useAI.mockReturnValue({ isAIIntegrationEnabled: { value: false } }); + const { conversationHotKeys } = useConversationHotKeys(); + const aiAssistAction = conversationHotKeys.value.find( + action => action.id === 'ai_assist' + ); + expect(aiAssistAction).toBeUndefined(); + }); + + it('should dispatch actions when handlers are called', () => { + const { conversationHotKeys } = useConversationHotKeys(); + const assignAgentAction = conversationHotKeys.value.find( + action => action.id === 'assign_an_agent' + ); + expect(assignAgentAction).toBeDefined(); + + if (assignAgentAction && assignAgentAction.children) { + const childAction = conversationHotKeys.value.find( + action => action.id === assignAgentAction.children[0] + ); + if (childAction && childAction.handler) { + childAction.handler({ agentInfo: { id: 2 } }); + expect(store.dispatch).toHaveBeenCalledWith('assignAgent', { + conversationId: 1, + agentId: 2, + }); + } + } + }); + + it('should return snooze actions when in snooze context', () => { + store.getters.getContextMenuChatId = 1; + useMapGetter.mockImplementation(key => ({ + value: store.getters[key], + })); + useRoute.mockReturnValue({ name: 'inbox_conversation' }); + + const { conversationHotKeys } = useConversationHotKeys(); + const snoozeAction = conversationHotKeys.value.find(action => + action.id.includes('snooze_conversation') + ); + expect(snoozeAction).toBeDefined(); + }); + + it('should return the correct label actions when there are active labels', () => { + const { conversationHotKeys } = useConversationHotKeys(); + const addLabelAction = conversationHotKeys.value.find( + action => action.id === 'add_a_label_to_the_conversation' + ); + const removeLabelAction = conversationHotKeys.value.find( + action => action.id === 'remove_a_label_to_the_conversation' + ); + + expect(addLabelAction).toBeDefined(); + expect(removeLabelAction).toBeDefined(); + }); + + it('should return only add label actions when there are no active labels', () => { + useConversationLabels.mockReturnValue({ + activeLabels: { value: [] }, + inactiveLabels: { value: [{ title: 'inactive_label' }] }, + addLabelToConversation: vi.fn(), + removeLabelFromConversation: vi.fn(), + }); + + const { conversationHotKeys } = useConversationHotKeys(); + const addLabelAction = conversationHotKeys.value.find( + action => action.id === 'add_a_label_to_the_conversation' + ); + const removeLabelAction = conversationHotKeys.value.find( + action => action.id === 'remove_a_label_to_the_conversation' + ); + + expect(addLabelAction).toBeDefined(); + expect(removeLabelAction).toBeUndefined(); + }); + + it('should return the correct team assignment actions', () => { + const { conversationHotKeys } = useConversationHotKeys(); + const assignTeamAction = conversationHotKeys.value.find( + action => action.id === 'assign_a_team' + ); + + expect(assignTeamAction).toBeDefined(); + expect(assignTeamAction.children.length).toBe(mockTeamsList.length); + }); + + it('should return the correct priority assignment actions', () => { + const { conversationHotKeys } = useConversationHotKeys(); + const assignPriorityAction = conversationHotKeys.value.find( + action => action.id === 'assign_priority' + ); + + expect(assignPriorityAction).toBeDefined(); + expect(assignPriorityAction.children.length).toBe(4); + }); + + it('should return the correct conversation additional actions', () => { + const { conversationHotKeys } = useConversationHotKeys(); + const muteAction = conversationHotKeys.value.find( + action => action.id === 'mute_conversation' + ); + const sendTranscriptAction = conversationHotKeys.value.find( + action => action.id === 'send_transcript' + ); + + expect(muteAction).toBeDefined(); + expect(sendTranscriptAction).toBeDefined(); + }); + + it('should return unmute action when conversation is muted', () => { + store.getters.getSelectedChat = { ...mockCurrentChat, muted: true }; + const { conversationHotKeys } = useConversationHotKeys(); + const unmuteAction = conversationHotKeys.value.find( + action => action.id === 'unmute_conversation' + ); + + expect(unmuteAction).toBeDefined(); + }); + + it('should not return conversation hot keys when not in conversation or inbox route', () => { + useRoute.mockReturnValue({ name: 'some_other_route' }); + const { conversationHotKeys } = useConversationHotKeys(); + + expect(conversationHotKeys.value.length).toBe(0); + }); +});