# Pull Request Template ## Description This is the third and final PR in a series of PRs for Introducing unread counts in the sidebar for inboxes and labels. In this PR: * Added frontend changes to show the badges for unread counts for Inboxes and Labels * Added specs for the changes Issue: https://linear.app/chatwoot/issue/CW-6851/support-unread-conversation-counts ## Type of change Please delete options that are not relevant. - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality not to work as expected) - [ ] This change requires a documentation update ## How Has This Been Tested? Tested this locally. Cases to test: * Send a message from the widget and see if the count changes * Mark a conversation as unread and see the count change for inbox * Open an unread conversation as agent and see the count go down * Add a label to an unread conversation from sidebar right click action without opening the conversation and see the count of un-reads on the label change Added the screenshot of how it will look like <img width="614" height="990" alt="Screenshot 2026-05-05 at 7 00 11 PM" src="https://github.com/user-attachments/assets/99fbaa9f-bcf2-4d8d-86e2-5727f652a9dd" /> ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --------- Co-authored-by: Sojan Jose <sojan@pepalo.com>
164 lines
4.5 KiB
JavaScript
164 lines
4.5 KiB
JavaScript
import { describe, it, beforeEach, afterEach, expect, vi } from 'vitest';
|
|
import ActionCableConnector from '../actionCable';
|
|
|
|
vi.mock('shared/helpers/mitt', () => ({
|
|
emitter: {
|
|
emit: vi.fn(),
|
|
},
|
|
}));
|
|
|
|
vi.mock('dashboard/composables/useImpersonation', () => ({
|
|
useImpersonation: () => ({
|
|
isImpersonating: { value: false },
|
|
}),
|
|
}));
|
|
|
|
global.chatwootConfig = {
|
|
websocketURL: 'wss://test.chatwoot.com',
|
|
};
|
|
|
|
describe('ActionCableConnector - Copilot Tests', () => {
|
|
let store;
|
|
let actionCable;
|
|
let mockDispatch;
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
mockDispatch = vi.fn();
|
|
store = {
|
|
$store: {
|
|
dispatch: mockDispatch,
|
|
getters: {
|
|
getCurrentAccountId: 1,
|
|
'accounts/isFeatureEnabledonAccount': vi.fn(() => true),
|
|
},
|
|
},
|
|
};
|
|
|
|
actionCable = ActionCableConnector.init(store.$store, 'test-token');
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
});
|
|
describe('copilot event handlers', () => {
|
|
it('should register the copilot.message.created event handler', () => {
|
|
expect(Object.keys(actionCable.events)).toContain(
|
|
'copilot.message.created'
|
|
);
|
|
expect(actionCable.events['copilot.message.created']).toBe(
|
|
actionCable.onCopilotMessageCreated
|
|
);
|
|
});
|
|
|
|
it('should handle the copilot.message.created event through the ActionCable system', () => {
|
|
const copilotData = {
|
|
id: 2,
|
|
content: 'This is a copilot message from ActionCable',
|
|
conversation_id: 456,
|
|
created_at: '2025-05-27T15:58:04-06:00',
|
|
account_id: 1,
|
|
};
|
|
actionCable.onReceived({
|
|
event: 'copilot.message.created',
|
|
data: copilotData,
|
|
});
|
|
expect(mockDispatch).toHaveBeenCalledWith(
|
|
'copilotMessages/upsert',
|
|
copilotData
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('conversation unread count event handlers', () => {
|
|
it('should register the conversation.unread_count_changed event handler', () => {
|
|
expect(Object.keys(actionCable.events)).toContain(
|
|
'conversation.unread_count_changed'
|
|
);
|
|
expect(actionCable.events['conversation.unread_count_changed']).toBe(
|
|
actionCable.onConversationUnreadCountChanged
|
|
);
|
|
});
|
|
|
|
it('should refetch unread counts when unread count changes', () => {
|
|
actionCable.onReceived({
|
|
event: 'conversation.unread_count_changed',
|
|
data: { account_id: 1 },
|
|
});
|
|
|
|
expect(mockDispatch).toHaveBeenCalledWith('conversationUnreadCounts/get');
|
|
});
|
|
|
|
it('does not refetch unread counts when unread count feature is disabled', () => {
|
|
store.$store.getters[
|
|
'accounts/isFeatureEnabledonAccount'
|
|
].mockReturnValue(false);
|
|
|
|
actionCable.onReceived({
|
|
event: 'conversation.unread_count_changed',
|
|
data: { account_id: 1 },
|
|
});
|
|
|
|
expect(mockDispatch).not.toHaveBeenCalledWith(
|
|
'conversationUnreadCounts/get'
|
|
);
|
|
});
|
|
|
|
it('should throttle unread count refetches for repeated events', () => {
|
|
vi.useFakeTimers();
|
|
vi.setSystemTime(new Date('2026-01-01T00:00:00Z'));
|
|
|
|
actionCable.onReceived({
|
|
event: 'conversation.unread_count_changed',
|
|
data: { account_id: 1 },
|
|
});
|
|
actionCable.onReceived({
|
|
event: 'conversation.unread_count_changed',
|
|
data: { account_id: 1 },
|
|
});
|
|
actionCable.onReceived({
|
|
event: 'conversation.unread_count_changed',
|
|
data: { account_id: 1 },
|
|
});
|
|
|
|
expect(mockDispatch).toHaveBeenCalledTimes(1);
|
|
|
|
vi.advanceTimersByTime(4999);
|
|
expect(mockDispatch).toHaveBeenCalledTimes(1);
|
|
|
|
vi.advanceTimersByTime(1);
|
|
expect(mockDispatch).toHaveBeenCalledTimes(2);
|
|
expect(mockDispatch).toHaveBeenLastCalledWith(
|
|
'conversationUnreadCounts/get'
|
|
);
|
|
});
|
|
|
|
it('clears pending unread count refetch before immediate refetch', () => {
|
|
vi.useFakeTimers();
|
|
vi.setSystemTime(new Date('2026-01-01T00:00:00Z'));
|
|
|
|
actionCable.onReceived({
|
|
event: 'conversation.unread_count_changed',
|
|
data: { account_id: 1 },
|
|
});
|
|
|
|
vi.advanceTimersByTime(1000);
|
|
actionCable.onReceived({
|
|
event: 'conversation.unread_count_changed',
|
|
data: { account_id: 1 },
|
|
});
|
|
|
|
vi.setSystemTime(new Date('2026-01-01T00:00:06Z'));
|
|
actionCable.onReceived({
|
|
event: 'conversation.unread_count_changed',
|
|
data: { account_id: 1 },
|
|
});
|
|
|
|
expect(mockDispatch).toHaveBeenCalledTimes(2);
|
|
|
|
vi.advanceTimersByTime(4000);
|
|
expect(mockDispatch).toHaveBeenCalledTimes(2);
|
|
});
|
|
});
|
|
});
|