From fac4675dc56300ac905927ee8966f7ff2921c434 Mon Sep 17 00:00:00 2001 From: Muhsin Date: Wed, 11 Mar 2026 20:50:08 +0400 Subject: [PATCH] Fix inbound voice call ownership flow --- .../message/bubbles/VoiceCall.spec.js | 80 ++++++++++++ .../message/bubbles/VoiceCall.vue | 84 ++++++++----- .../composables/spec/useCallSession.spec.js | 79 ++++++++++++ .../dashboard/composables/useCallSession.js | 15 ++- .../dashboard/helper/specs/voice.spec.js | 116 ++++++++++++++++++ app/javascript/dashboard/helper/voice.js | 68 ++++++++-- .../i18n/locale/en/conversation.json | 1 + .../store/modules/conversations/actions.js | 12 +- .../specs/conversations/actions.spec.js | 6 + app/javascript/dashboard/stores/calls.js | 14 +++ .../api/v1/accounts/conference_controller.rb | 22 +++- .../provider/twilio/conference_service.rb | 18 ++- .../v1/accounts/conference_controller_spec.rb | 36 +++++- .../twilio/conference_service_spec.rb | 17 +++ 14 files changed, 518 insertions(+), 50 deletions(-) create mode 100644 app/javascript/dashboard/components-next/message/bubbles/VoiceCall.spec.js create mode 100644 app/javascript/dashboard/composables/spec/useCallSession.spec.js create mode 100644 app/javascript/dashboard/helper/specs/voice.spec.js diff --git a/app/javascript/dashboard/components-next/message/bubbles/VoiceCall.spec.js b/app/javascript/dashboard/components-next/message/bubbles/VoiceCall.spec.js new file mode 100644 index 000000000..453a0554b --- /dev/null +++ b/app/javascript/dashboard/components-next/message/bubbles/VoiceCall.spec.js @@ -0,0 +1,80 @@ +import { ref } from 'vue'; +import { mount } from '@vue/test-utils'; +import VoiceCallBubble from './VoiceCall.vue'; +import { MESSAGE_TYPES, VOICE_CALL_STATUS } from '../constants'; + +let messageContext; + +vi.mock('../provider.js', () => ({ + useMessageContext: () => messageContext, +})); + +vi.mock('vue-i18n', () => ({ + useI18n: () => ({ + t: (key, params = {}) => { + const translations = { + 'CONVERSATION.VOICE_CALL.CALL_IN_PROGRESS': 'Call in progress', + 'CONVERSATION.VOICE_CALL.AGENT_ANSWERED': `${params.agentName} answered`, + 'CONVERSATION.VOICE_CALL.YOU_ANSWERED': 'You answered', + 'CONVERSATION.VOICE_CALL.THEY_ANSWERED': 'They answered', + 'CONVERSATION.VOICE_CALL.NOT_ANSWERED_YET': 'Not answered yet', + 'CONVERSATION.VOICE_CALL.CALL_ENDED': 'Call ended', + 'CONVERSATION.VOICE_CALL.NO_ANSWER': 'No answer', + }; + + return translations[key] || key; + }, + }), +})); + +const mountComponent = () => + mount(VoiceCallBubble, { + global: { + stubs: { + BaseBubble: { + template: '
', + }, + Icon: true, + }, + mocks: { + $t: key => key, + }, + }, + }); + +describe('VoiceCall.vue', () => { + beforeEach(() => { + messageContext = { + contentAttributes: ref({ + data: { + status: VOICE_CALL_STATUS.IN_PROGRESS, + meta: {}, + }, + }), + currentUserId: ref(1), + messageType: ref(MESSAGE_TYPES.INCOMING), + }; + }); + + it('shows the answering agent name when another agent answers the call', () => { + messageContext.contentAttributes.value.data.meta.joinedBy = { + id: 2, + name: 'Ben', + }; + + const wrapper = mountComponent(); + + expect(wrapper.text()).toContain('Ben answered'); + }); + + it('shows "You answered" when the current user answered the call', () => { + messageContext.contentAttributes.value.data.meta.joinedBy = { + id: 1, + name: 'John', + }; + + const wrapper = mountComponent(); + + expect(wrapper.text()).toContain('You answered'); + }); +}); diff --git a/app/javascript/dashboard/components-next/message/bubbles/VoiceCall.vue b/app/javascript/dashboard/components-next/message/bubbles/VoiceCall.vue index 5a7d39a4e..170f66474 100644 --- a/app/javascript/dashboard/components-next/message/bubbles/VoiceCall.vue +++ b/app/javascript/dashboard/components-next/message/bubbles/VoiceCall.vue @@ -1,21 +1,12 @@